diff --git a/toolkit/components/places/BookmarkHTMLUtils.jsm b/toolkit/components/places/BookmarkHTMLUtils.jsm index 9c0995107758..a0edb4c2327f 100644 --- a/toolkit/components/places/BookmarkHTMLUtils.jsm +++ b/toolkit/components/places/BookmarkHTMLUtils.jsm @@ -664,13 +664,14 @@ BookmarkImporter.prototype = { if (frame.previousFeed) { // The is a live bookmark. We create it here since in HandleLinkBegin we // don't know the title. - PlacesUtils.livemarks.addLivemark({ + let lmPromise = PlacesUtils.livemarks.addLivemark({ "title": frame.previousText, "parentId": frame.containerId, "index": PlacesUtils.bookmarks.DEFAULT_INDEX, "feedURI": frame.previousFeed, "siteURI": frame.previousLink, - }).then(null, Cu.reportError); + }); + this._importPromises.push(lmPromise); } else if (frame.previousLink) { // This is a common bookmark. PlacesUtils.bookmarks.setItemTitle(frame.previousId, diff --git a/toolkit/components/places/BookmarkJSONUtils.jsm b/toolkit/components/places/BookmarkJSONUtils.jsm index c0f9ec6519d2..b2dda86d390a 100644 --- a/toolkit/components/places/BookmarkJSONUtils.jsm +++ b/toolkit/components/places/BookmarkJSONUtils.jsm @@ -411,7 +411,7 @@ BookmarkImporter.prototype = { }); if (feedURI) { - PlacesUtils.livemarks.addLivemark({ + let lmPromise = PlacesUtils.livemarks.addLivemark({ title: aData.title, feedURI: feedURI, parentId: aContainer, @@ -424,7 +424,8 @@ BookmarkImporter.prototype = { PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded); if (aData.annos && aData.annos.length) PlacesUtils.setAnnotationsForItem(id, aData.annos); - }, Cu.reportError); + }); + this._importPromises.push(lmPromise); } } else { id = PlacesUtils.bookmarks.createFolder( diff --git a/toolkit/components/places/Bookmarks.jsm b/toolkit/components/places/Bookmarks.jsm index 2697e6c576f6..7bdd1ffd6088 100644 --- a/toolkit/components/places/Bookmarks.jsm +++ b/toolkit/components/places/Bookmarks.jsm @@ -1065,7 +1065,9 @@ function removeSameValueProperties(dest, src) { * the URL object to convert. * @return nsIURI for the given URL. */ -function toURI(url) NetUtil.newURI(url.href); +function toURI(url) { + return NetUtil.newURI(url.href); +} /** * Convert a Date object to a PRTime (microseconds). @@ -1074,7 +1076,9 @@ function toURI(url) NetUtil.newURI(url.href); * the Date object to convert. * @return microseconds from the epoch. */ -function toPRTime(date) date * 1000; +function toPRTime(date) { + return date * 1000; +} /** * Convert a PRTime to a Date object. @@ -1083,7 +1087,9 @@ function toPRTime(date) date * 1000; * microseconds from the epoch. * @return a Date object. */ -function toDate(time) new Date(parseInt(time / 1000)); +function toDate(time) { + return new Date(parseInt(time / 1000)); +} /** * Convert an array of mozIStorageRow objects to an array of bookmark objects. @@ -1216,8 +1222,12 @@ function validateBookmarkObject(input, behavior={}) { } for (let prop in input) { - if (required.has(prop)) + if (required.has(prop)) { required.delete(prop); + } else if (input[prop] === undefined) { + // Skip undefined properties that are not required. + continue; + } if (VALIDATORS.hasOwnProperty(prop)) { try { normalizedInput[prop] = VALIDATORS[prop](input[prop], input); @@ -1264,8 +1274,8 @@ let updateFrecency = Task.async(function* (db, urls) { let removeOrphanAnnotations = Task.async(function* (db) { yield db.executeCached( `DELETE FROM moz_items_annos - WHERE id IN (SELECT a.id from moz_items_annos a - LEFT JOIN moz_bookmarks b ON a.item_id = b.id + WHERE id IN (SELECT a.id from moz_items_annos a + LEFT JOIN moz_bookmarks b ON a.item_id = b.id WHERE b.id ISNULL) `); yield db.executeCached( @@ -1293,8 +1303,9 @@ let removeAnnotationsForItem = Task.async(function* (db, itemId) { yield db.executeCached( `DELETE FROM moz_anno_attributes WHERE id IN (SELECT n.id from moz_anno_attributes n - LEFT JOIN moz_items_annos a ON a.anno_attribute_id = n.id - WHERE a.id ISNULL) + LEFT JOIN moz_annos a1 ON a1.anno_attribute_id = n.id + LEFT JOIN moz_items_annos a2 ON a2.anno_attribute_id = n.id + WHERE a1.id ISNULL AND a2.id ISNULL) `); }); diff --git a/toolkit/components/places/mozIAsyncLivemarks.idl b/toolkit/components/places/mozIAsyncLivemarks.idl index 035dea0080a2..e84ecca8e221 100644 --- a/toolkit/components/places/mozIAsyncLivemarks.idl +++ b/toolkit/components/places/mozIAsyncLivemarks.idl @@ -6,13 +6,12 @@ interface nsIURI; -interface mozILivemarkCallback; interface mozILivemarkInfo; interface mozILivemark; interface nsINavHistoryResultObserver; -[scriptable, uuid(5B48E5A2-F07A-4E64-A935-C722A3D60B65)] +[scriptable, uuid(672387b7-a75d-4e8f-9b49-5c1dcbfff46b)] interface mozIAsyncLivemarks : nsISupports { /** @@ -21,18 +20,12 @@ interface mozIAsyncLivemarks : nsISupports * @param aLivemarkInfo * mozILivemarkInfo object containing at least title, parentId, * index and feedURI of the livemark to create. - * @param [optional] aCallback - * Invoked when the creation process is done. In case of failure will - * receive an error code. + * * @return {Promise} * @throws NS_ERROR_INVALID_ARG if the supplied information is insufficient * for the creation. - * @deprecated passing a callback is deprecated. Moreover, for backwards - * compatibility reasons, when a callback is provided this method - * won't return a promise. */ - jsval addLivemark(in jsval aLivemarkInfo, - [optional] in mozILivemarkCallback aCallback); + jsval addLivemark(in jsval aLivemarkInfo); /** * Removes an existing livemark. @@ -40,18 +33,11 @@ interface mozIAsyncLivemarks : nsISupports * @param aLivemarkInfo * mozILivemarkInfo object containing either an id or a guid of the * livemark to remove. - * @param [optional] aCallback - * Invoked when the removal process is done. In case of failure will - * receive an error code. * * @return {Promise} * @throws NS_ERROR_INVALID_ARG if the id/guid is invalid. - * @deprecated passing a callback is deprecated. Moreover, for backwards - * compatibility reasons, when a callback is provided this method - * won't return a promise. */ - jsval removeLivemark(in jsval aLivemarkInfo, - [optional] in mozILivemarkCallback aCallback); + jsval removeLivemark(in jsval aLivemarkInfo); /** * Gets an existing livemark. @@ -59,19 +45,12 @@ interface mozIAsyncLivemarks : nsISupports * @param aLivemarkInfo * mozILivemarkInfo object containing either an id or a guid of the * livemark to retrieve. - * @param [optional] aCallback - * Invoked when the fetching process is done. In case of failure will - * receive an error code. * * @return {Promise} * @throws NS_ERROR_INVALID_ARG if the id/guid is invalid or an invalid * callback is provided. - * @deprecated passing a callback is deprecated. Moreover, for backwards - * compatibility reasons, when a callback is provided this method - * won't return a promise. */ - jsval getLivemark(in jsval aLivemarkInfo, - [optional] in mozILivemarkCallback aCallback); + jsval getLivemark(in jsval aLivemarkInfo); /** * Reloads all livemarks if they are expired or if forced to do so. @@ -85,27 +64,13 @@ interface mozIAsyncLivemarks : nsISupports void reloadLivemarks([optional]in boolean aForceUpdate); }; -[scriptable, function, uuid(62a426f9-39a6-42f0-ad48-b7404d48188f)] -interface mozILivemarkCallback : nsISupports -{ - /** - * Invoked when a livemark is added, removed or retrieved. - * - * @param aStatus - * Whether the request was completed successfully. - * Use Components.isSuccessCode(aStatus) to check this. - * @param aLivemark - * A mozILivemark object representing the livemark, or null on removal. - */ - void onCompletion(in nsresult aStatus, - in mozILivemark aLivemark); -}; - -[scriptable, uuid(E52B2273-729D-4EBC-A039-E9CD9E18FF86)] +[scriptable, uuid(3a3c5e8f-ec4a-4086-ae0a-d16420d30c9f)] interface mozILivemarkInfo : nsISupports { /** * Id of the bookmarks folder representing this livemark. + * + * @deprecated Use guid instead. */ readonly attribute long long id; @@ -121,9 +86,16 @@ interface mozILivemarkInfo : nsISupports /** * Id of the bookmarks parent folder containing this livemark. + * + * @deprecated Use parentGuid instead. */ readonly attribute long long parentId; + /** + * Guid of the bookmarks parent folder containing this livemark. + */ + readonly attribute long long parentGuid; + /** * The position of this livemark in the bookmarks parent folder. */ diff --git a/toolkit/components/places/nsLivemarkService.js b/toolkit/components/places/nsLivemarkService.js index 6cdc01f5751f..01a82ac581c6 100644 --- a/toolkit/components/places/nsLivemarkService.js +++ b/toolkit/components/places/nsLivemarkService.js @@ -2,13 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; +const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; //////////////////////////////////////////////////////////////////////////////// -//// Modules +//// Modules and services. Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); @@ -16,31 +13,20 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Promise", - "resource://gre/modules/Promise.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Deprecated", "resource://gre/modules/Deprecated.jsm"); -//////////////////////////////////////////////////////////////////////////////// -//// Services - -XPCOMUtils.defineLazyServiceGetter(this, "secMan", - "@mozilla.org/scriptsecuritymanager;1", - "nsIScriptSecurityManager"); XPCOMUtils.defineLazyGetter(this, "asyncHistory", function () { // Lazily add an history observer when it's actually needed. PlacesUtils.history.addObserver(PlacesUtils.livemarks, true); - return Cc["@mozilla.org/browser/history;1"].getService(Ci.mozIAsyncHistory); + return PlacesUtils.asyncHistory; }); //////////////////////////////////////////////////////////////////////////////// //// Constants -// Security flags for checkLoadURIWithPrincipal. -const SEC_FLAGS = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL; - // Delay between reloads of consecute livemarks. const RELOAD_DELAY_MS = 500; // Expire livemarks after this time. @@ -48,81 +34,97 @@ const EXPIRE_TIME_MS = 3600000; // 1 hour. // Expire livemarks after this time on error. const ONERROR_EXPIRE_TIME_MS = 300000; // 5 minutes. +//////////////////////////////////////////////////////////////////////////////// +//// Livemarks cache. + +XPCOMUtils.defineLazyGetter(this, "CACHE_SQL", () => { + function getAnnoSQLFragment(aAnnoParam) { + return `SELECT a.content + FROM moz_items_annos a + JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id + WHERE a.item_id = b.id + AND n.name = ${aAnnoParam}`; + } + + return `SELECT b.id, b.title, b.parent As parentId, b.position AS 'index', + b.guid, b.dateAdded, b.lastModified, p.guid AS parentGuid, + ( ${getAnnoSQLFragment(":feedURI_anno")} ) AS feedURI, + ( ${getAnnoSQLFragment(":siteURI_anno")} ) AS siteURI + FROM moz_bookmarks b + JOIN moz_bookmarks p ON b.parent = p.id + JOIN moz_items_annos a ON a.item_id = b.id + JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id + WHERE b.type = :folder_type + AND n.name = :feedURI_anno`; +}); + +XPCOMUtils.defineLazyGetter(this, "gLivemarksCachePromised", + Task.async(function* () { + let livemarksMap = new Map(); + let conn = yield PlacesUtils.promiseDBConnection(); + let rows = yield conn.executeCached(CACHE_SQL, + { folder_type: Ci.nsINavBookmarksService.TYPE_FOLDER, + feedURI_anno: PlacesUtils.LMANNO_FEEDURI, + siteURI_anno: PlacesUtils.LMANNO_SITEURI }); + for (let row of rows) { + let siteURI = row.getResultByName("siteURI"); + let livemark = new Livemark({ + id: row.getResultByName("id"), + guid: row.getResultByName("guid"), + title: row.getResultByName("title"), + parentId: row.getResultByName("parent"), + parentGuid: row.getResultByName("parentGuid"), + index: row.getResultByName("position"), + dateAdded: row.getResultByName("dateAdded"), + lastModified: row.getResultByName("lastModified"), + feedURI: NetUtil.newURI(row.getResultByName("feedURI")), + siteURI: siteURI ? NetUtil.newURI(siteURI) : null + }); + livemarksMap.set(livemark.guid, livemark); + } + return livemarksMap; + }) +); + +/** + * Convert a Date object to a PRTime (microseconds). + * + * @param date + * the Date object to convert. + * @return microseconds from the epoch. + */ +function toPRTime(date) { + return date * 1000; +} + +/** + * Convert a PRTime to a Date object. + * + * @param time + * microseconds from the epoch. + * @return a Date object or undefined if time was not defined. + */ +function toDate(time) { + return time ? new Date(parseInt(time / 1000)) : undefined; +} + //////////////////////////////////////////////////////////////////////////////// //// LivemarkService -function LivemarkService() -{ +function LivemarkService() { // Cleanup on shutdown. Services.obs.addObserver(this, PlacesUtils.TOPIC_SHUTDOWN, true); - // Observe bookmarks and history, but don't init the services just for that. + // Observe bookmarks but don't init the service just for that. PlacesUtils.addLazyBookmarkObserver(this, true); - - // Asynchronously build the livemarks cache. - this._cacheReadyPromise = - this._ensureAsynchronousCache().then(null, Cu.reportError); } LivemarkService.prototype = { - // Cache of Livemark objects, hashed by bookmarks folder ids. - _livemarks: {}, - // Hash associating guids to bookmarks folder ids. - _guids: {}, - - get _populateCacheSQL() - { - function getAnnoSQLFragment(aAnnoParam) { - return `SELECT a.content - FROM moz_items_annos a - JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id - WHERE a.item_id = b.id - AND n.name = ${aAnnoParam}`; - } - - return `SELECT b.id, b.title, b.parent, b.position, b.guid, - b.dateAdded, b.lastModified, - ( ${getAnnoSQLFragment(":feedURI_anno")} ) AS feedURI, - ( ${getAnnoSQLFragment(":siteURI_anno")} ) AS siteURI - FROM moz_bookmarks b - JOIN moz_items_annos a ON a.item_id = b.id - JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id - WHERE b.type = :folder_type - AND n.name = :feedURI_anno`; - }, - - _ensureAsynchronousCache: Task.async(function* () { - let conn = yield PlacesUtils.promiseDBConnection(); - yield conn.executeCached(this._populateCacheSQL, - { folder_type: Ci.nsINavBookmarksService.TYPE_FOLDER, - feedURI_anno: PlacesUtils.LMANNO_FEEDURI, - siteURI_anno: PlacesUtils.LMANNO_SITEURI }, - row => { - let id = row.getResultByName("id"); - let guid = row.getResultByName("guid"); - let siteURL = row.getResultByName("siteURI"); - this._livemarks[id] = - new Livemark({ id: id, - guid: guid, - title: row.getResultByName("title"), - parentId: row.getResultByName("parent"), - index: row.getResultByName("position"), - dateAdded: row.getResultByName("dateAdded"), - lastModified: row.getResultByName("lastModified"), - feedURI: NetUtil.newURI(row.getResultByName("feedURI")), - siteURI: siteURL ? NetUtil.newURI(siteURL) : null }); - this._guids[guid] = id; - }); - }), - - _onCacheReady: function LS__onCacheReady(aCallback) - { - this._cacheReadyPromise.then(aCallback); - }, + // This is just an helper for code readability. + _promiseLivemarksMap: () => gLivemarksCachePromised, _reloading: false, - _startReloadTimer: function LS__startReloadTimer() - { + _startReloadTimer(livemarksMap, forceUpdate, reloaded) { if (this._reloadTimer) { this._reloadTimer.cancel(); } @@ -130,212 +132,139 @@ LivemarkService.prototype = { this._reloadTimer = Cc["@mozilla.org/timer;1"] .createInstance(Ci.nsITimer); } + this._reloading = true; - this._reloadTimer.initWithCallback(this._reloadNextLivemark.bind(this), - RELOAD_DELAY_MS, - Ci.nsITimer.TYPE_ONE_SHOT); + this._reloadTimer.initWithCallback(() => { + // Find first livemark to be reloaded. + for (let [ guid, livemark ] of livemarksMap) { + if (!reloaded.has(guid)) { + reloaded.add(guid); + livemark.reload(forceUpdate); + this._startReloadTimer(livemarksMap, forceUpdate, reloaded); + return; + } + } + // All livemarks have been reloaded. + this._reloading = false; + }, RELOAD_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT); }, ////////////////////////////////////////////////////////////////////////////// //// nsIObserver - observe: function LS_observe(aSubject, aTopic, aData) - { + observe(aSubject, aTopic, aData) { if (aTopic == PlacesUtils.TOPIC_SHUTDOWN) { - if (this._pendingStmt) { - this._pendingStmt.cancel(); - this._pendingStmt = null; - // Initialization never finished, so just bail out. - return; - } - if (this._reloadTimer) { this._reloading = false; this._reloadTimer.cancel(); delete this._reloadTimer; } - // Stop any ongoing update. - for each (let livemark in this._livemarks) { - livemark.terminate(); - } - this._livemarks = {}; + // Stop any ongoing network fetch. + this._promiseLivemarksMap().then(livemarksMap => { + for (let livemark of livemarksMap.values()) { + livemark.terminate(); + } + }); } }, ////////////////////////////////////////////////////////////////////////////// //// mozIAsyncLivemarks - addLivemark: function LS_addLivemark(aLivemarkInfo, - aLivemarkCallback) - { - // Must provide at least non-null parentId, index and feedURI. - if (!aLivemarkInfo || - ("parentId" in aLivemarkInfo && aLivemarkInfo.parentId < 1) || - !("index" in aLivemarkInfo) || aLivemarkInfo.index < Ci.nsINavBookmarksService.DEFAULT_INDEX || + addLivemark(aLivemarkInfo) { + if (!aLivemarkInfo) { + throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG); + } + let hasParentId = "parentId" in aLivemarkInfo; + let hasParentGuid = "parentGuid" in aLivemarkInfo; + let hasIndex = "index" in aLivemarkInfo; + // Must provide at least non-null parent guid/id, index and feedURI. + if ((!hasParentId && !hasParentGuid) || + (hasParentId && aLivemarkInfo.parentId < 1) || + (hasParentGuid &&!/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.parentGuid)) || + (hasIndex && aLivemarkInfo.index < Ci.nsINavBookmarksService.DEFAULT_INDEX) || !(aLivemarkInfo.feedURI instanceof Ci.nsIURI) || (aLivemarkInfo.siteURI && !(aLivemarkInfo.siteURI instanceof Ci.nsIURI)) || (aLivemarkInfo.guid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid))) { - throw Cr.NS_ERROR_INVALID_ARG; + throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG); } - if (aLivemarkCallback) { - Deprecated.warning("Passing a callback to Livermarks methods is deprecated. " + - "Please use the returned promise instead.", - "https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules/Promise.jsm"); - } + return Task.spawn(function* () { + if (!aLivemarkInfo.parentGuid) + aLivemarkInfo.parentGuid = yield PlacesUtils.promiseItemGuid(aLivemarkInfo.parentId); + + let livemarksMap = yield this._promiseLivemarksMap(); - // The addition is done synchronously due to the fact importExport service - // and JSON backups require that. The notification is async though. - // Once bookmarks are async, this may be properly fixed. - let deferred = Promise.defer(); - let addLivemarkEx = null; - let livemark = null; - try { // Disallow adding a livemark inside another livemark. - if (aLivemarkInfo.parentId in this._livemarks) { - throw new Components.Exception("", Cr.NS_ERROR_INVALID_ARG); + if (livemarksMap.has(aLivemarkInfo.parentGuid)) { + throw new Components.Exception("Cannot create a livemark inside a livemark", Cr.NS_ERROR_INVALID_ARG); } - // Don't pass unexpected input data to the livemark constructor. - livemark = new Livemark({ title: aLivemarkInfo.title - , parentId: aLivemarkInfo.parentId - , index: aLivemarkInfo.index - , feedURI: aLivemarkInfo.feedURI - , siteURI: aLivemarkInfo.siteURI - , guid: aLivemarkInfo.guid - , dateAdded: aLivemarkInfo.dateAdded - , lastModified: aLivemarkInfo.lastModified - }); - if (this._itemAdded && this._itemAdded.id == livemark.id) { - livemark.index = this._itemAdded.index; - livemark.guid = this._itemAdded.guid; - if (!aLivemarkInfo.dateAdded) { - livemark.dateAdded = this._itemAdded.dateAdded; - } - if (!aLivemarkInfo.lastModified) { - livemark.lastModified = this._itemAdded.lastModified; - } - } - - // Updating the cache even if it has not yet been populated doesn't - // matter since it will just be overwritten. - this._livemarks[livemark.id] = livemark; - this._guids[livemark.guid] = livemark.id; - } - catch (ex) { - addLivemarkEx = ex; - livemark = null; - } - finally { - this._onCacheReady( () => { - if (addLivemarkEx) { - if (aLivemarkCallback) { - try { - aLivemarkCallback.onCompletion(addLivemarkEx.result, livemark); - } - catch(ex2) { } - } else { - deferred.reject(addLivemarkEx); - } - } - else { - if (aLivemarkCallback) { - try { - aLivemarkCallback.onCompletion(Cr.NS_OK, livemark); - } - catch(ex2) { } - } else { - deferred.resolve(livemark); - } - } + // Create a new livemark. + let folder = yield PlacesUtils.bookmarks.insert({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: aLivemarkInfo.parentGuid, + title: aLivemarkInfo.title, + index: aLivemarkInfo.index, + guid: aLivemarkInfo.guid, + dateAdded: toDate(aLivemarkInfo.dateAdded) || toDate(aLivemarkInfo.lastModified), + lastModified: toDate(aLivemarkInfo.lastModified), }); - } - return aLivemarkCallback ? null : deferred.promise; + // Set feed and site URI annotations. + let id = yield PlacesUtils.promiseItemId(folder.guid); + + // Create the internal Livemark object. + let livemark = new Livemark({ id + , title: folder.title + , parentGuid: folder.parentGuid + , parentId: yield PlacesUtils.promiseItemId(folder.parentGuid) + , index: folder.index + , feedURI: aLivemarkInfo.feedURI + , siteURI: aLivemarkInfo.siteURI + , guid: folder.guid + , dateAdded: toPRTime(folder.dateAdded) + , lastModified: toPRTime(folder.lastModified) + }); + + livemark.writeFeedURI(aLivemarkInfo.feedURI); + if (aLivemarkInfo.siteURI) { + livemark.writeSiteURI(aLivemarkInfo.siteURI); + } + + livemarksMap.set(folder.guid, livemark); + + return livemark; + }.bind(this)); }, - removeLivemark: function LS_removeLivemark(aLivemarkInfo, aLivemarkCallback) - { + removeLivemark(aLivemarkInfo) { if (!aLivemarkInfo) { - throw Cr.NS_ERROR_INVALID_ARG; + throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG); } - // Accept either a guid or an id. - let id = aLivemarkInfo.guid || aLivemarkInfo.id; - if (("guid" in aLivemarkInfo && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) || - ("id" in aLivemarkInfo && aLivemarkInfo.id < 1) || - !id) { - throw Cr.NS_ERROR_INVALID_ARG; + let hasGuid = "guid" in aLivemarkInfo; + let hasId = "id" in aLivemarkInfo; + if ((hasGuid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) || + (hasId && aLivemarkInfo.id < 1) || + (!hasId && !hasGuid)) { + throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG); } - if (aLivemarkCallback) { - Deprecated.warning("Passing a callback to Livermarks methods is deprecated. " + - "Please use the returned promise instead.", - "https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules/Promise.jsm"); - } + return Task.spawn(function* () { + if (!aLivemarkInfo.guid) + aLivemarkInfo.guid = yield PlacesUtils.promiseItemGuid(aLivemarkInfo.id); - // Convert the guid to an id. - if (id in this._guids) { - id = this._guids[id]; - } + let livemarksMap = yield this._promiseLivemarksMap(); + if (!livemarksMap.has(aLivemarkInfo.guid)) + throw new Components.Exception("Invalid livemark", Cr.NS_ERROR_INVALID_ARG); - let deferred = Promise.defer(); - let removeLivemarkEx = null; - try { - if (!(id in this._livemarks)) { - throw new Components.Exception("", Cr.NS_ERROR_INVALID_ARG); - } - this._livemarks[id].remove(); - } - catch (ex) { - removeLivemarkEx = ex; - } - finally { - this._onCacheReady( () => { - if (removeLivemarkEx) { - if (aLivemarkCallback) { - try { - aLivemarkCallback.onCompletion(removeLivemarkEx.result, null); - } - catch(ex2) { } - } else { - deferred.reject(removeLivemarkEx); - } - } - else { - if (aLivemarkCallback) { - try { - aLivemarkCallback.onCompletion(Cr.NS_OK, null); - } - catch(ex2) { } - } else { - deferred.resolve(); - } - } - }); - } - - return aLivemarkCallback ? null : deferred.promise; + yield PlacesUtils.bookmarks.remove(aLivemarkInfo.guid); + }.bind(this)); }, - _reloaded: [], - _reloadNextLivemark: function LS__reloadNextLivemark() - { - this._reloading = false; - // Find first livemark to be reloaded. - for (let id in this._livemarks) { - if (this._reloaded.indexOf(id) == -1) { - this._reloaded.push(id); - this._livemarks[id].reload(this._forceUpdate); - this._startReloadTimer(); - break; - } - } - }, - - reloadLivemarks: function LS_reloadLivemarks(aForceUpdate) - { + reloadLivemarks(aForceUpdate) { // Check if there's a currently running reload, to save some useless work. let notWorthRestarting = this._forceUpdate || // We're already forceUpdating. @@ -345,149 +274,119 @@ LivemarkService.prototype = { return; } - this._onCacheReady( () => { + this._promiseLivemarksMap().then(livemarksMap => { this._forceUpdate = !!aForceUpdate; - this._reloaded = []; - // Livemarks reloads happen on a timer, and are delayed for performance - // reasons. - this._startReloadTimer(); + // Livemarks reloads happen on a timer for performance reasons. + this._startReloadTimer(livemarksMap, this._forceUpdate, new Set()); }); }, - getLivemark: function LS_getLivemark(aLivemarkInfo, aLivemarkCallback) - { + getLivemark(aLivemarkInfo) { if (!aLivemarkInfo) { - throw Cr.NS_ERROR_INVALID_ARG; + throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG); } // Accept either a guid or an id. - let id = aLivemarkInfo.guid || aLivemarkInfo.id; - if (("guid" in aLivemarkInfo && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) || - ("id" in aLivemarkInfo && aLivemarkInfo.id < 1) || - !id) { - throw Cr.NS_ERROR_INVALID_ARG; + let hasGuid = "guid" in aLivemarkInfo; + let hasId = "id" in aLivemarkInfo; + if ((hasGuid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) || + (hasId && aLivemarkInfo.id < 1) || + (!hasId && !hasGuid)) { + throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG); } - if (aLivemarkCallback) { - Deprecated.warning("Passing a callback to Livermarks methods is deprecated. " + - "Please use the returned promise instead.", - "https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules/Promise.jsm"); - } + return Task.spawn(function*() { + if (!aLivemarkInfo.guid) + aLivemarkInfo.guid = yield PlacesUtils.promiseItemGuid(aLivemarkInfo.id); - let deferred = Promise.defer(); - this._onCacheReady( () => { - // Convert the guid to an id. - if (id in this._guids) { - id = this._guids[id]; - } - if (id in this._livemarks) { - if (aLivemarkCallback) { - try { - aLivemarkCallback.onCompletion(Cr.NS_OK, this._livemarks[id]); - } catch (ex) {} - } else { - deferred.resolve(this._livemarks[id]); - } - } - else { - if (aLivemarkCallback) { - try { - aLivemarkCallback.onCompletion(Cr.NS_ERROR_INVALID_ARG, null); - } catch (ex) { } - } else { - deferred.reject(Components.Exception("", Cr.NS_ERROR_INVALID_ARG)); - } - } - }); + let livemarksMap = yield this._promiseLivemarksMap(); + if (!livemarksMap.has(aLivemarkInfo.guid)) + throw new Components.Exception("Invalid livemark", Cr.NS_ERROR_INVALID_ARG); - return aLivemarkCallback ? null : deferred.promise; + return livemarksMap.get(aLivemarkInfo.guid); + }.bind(this)); }, ////////////////////////////////////////////////////////////////////////////// //// nsINavBookmarkObserver - onBeginUpdateBatch: function () {}, - onEndUpdateBatch: function () {}, - onItemVisited: function () {}, + onBeginUpdateBatch() {}, + onEndUpdateBatch() {}, + onItemVisited() {}, + onItemAdded() {}, - _itemAdded: null, - onItemAdded: function LS_onItemAdded(aItemId, aParentId, aIndex, aItemType, - aURI, aTitle, aDateAdded, aGUID) - { - if (aItemType == Ci.nsINavBookmarksService.TYPE_FOLDER) { - this._itemAdded = { id: aItemId - , guid: aGUID - , index: aIndex - , dateAdded: aDateAdded - , lastModified: aDateAdded - }; - } - }, + onItemChanged(id, property, isAnno, value, lastModified, itemType, parentId, + guid, parentGuid) { + if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER) + return; - onItemChanged: function LS_onItemChanged(aItemId, aProperty, aIsAnno, aValue, - aLastModified, aItemType) - { - if (aItemType == Ci.nsINavBookmarksService.TYPE_FOLDER) { - if (this._itemAdded && this._itemAdded.id == aItemId) { - this._itemAdded.lastModified = aLastModified; - } - if (aItemId in this._livemarks) { - if (aProperty == "title") { - this._livemarks[aItemId].title = aValue; + this._promiseLivemarksMap().then(livemarksMap => { + if (livemarksMap.has(guid)) { + let livemark = livemarksMap.get(guid); + if (property == "title") { + livemark.title = value; } - else if (aProperty == "dateAdded") { - this._livemark[aItemId].dateAdded = parseInt(aValue, 10); - } - - this._livemarks[aItemId].lastModified = aLastModified; + livemark.lastModified = lastModified; } - } + }); }, - onItemMoved: function LS_onItemMoved(aItemId, aOldParentId, aOldIndex, - aNewParentId, aNewIndex, aItemType) - { - if (aItemType == Ci.nsINavBookmarksService.TYPE_FOLDER && - aItemId in this._livemarks) { - this._livemarks[aItemId].parentId = aNewParentId; - this._livemarks[aItemId].index = aNewIndex; - } + onItemMoved(id, parentId, oldIndex, newParentId, newIndex, itemType, guid, + oldParentGuid, newParentGuid) { + if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER) + return; + + this._promiseLivemarksMap().then(livemarksMap => { + if (livemarksMap.has(guid)) { + let livemark = livemarksMap.get(guid); + livemark.parentId = newParentId; + livemark.parentGuid = newParentGuid; + livemark.index = newIndex; + } + }); }, - onItemRemoved: function LS_onItemRemoved(aItemId, aParentId, aIndex, - aItemType, aURI, aGUID) - { - if (aItemType == Ci.nsINavBookmarksService.TYPE_FOLDER && - aItemId in this._livemarks) { - this._livemarks[aItemId].terminate(); - delete this._livemarks[aItemId]; - delete this._guids[aGUID]; - } + onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid) { + if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER) + return; + + this._promiseLivemarksMap().then(livemarksMap => { + if (livemarksMap.has(guid)) { + let livemark = livemarksMap.get(guid); + livemark.terminate(); + livemarksMap.delete(guid); + } + }); }, ////////////////////////////////////////////////////////////////////////////// //// nsINavHistoryObserver - onBeginUpdateBatch: function () {}, - onEndUpdateBatch: function () {}, - onPageChanged: function () {}, - onTitleChanged: function () {}, - onDeleteVisits: function () {}, + onPageChanged() {}, + onTitleChanged() {}, + onDeleteVisits() {}, + onClearHistory() { - for each (let livemark in this._livemarks) { - livemark.updateURIVisitedStatus(null, false); - } + this._promiseLivemarksMap().then(livemarksMap => { + for (let livemark of livemarksMap.values()) { + livemark.updateURIVisitedStatus(null, false); + } + }); }, - onDeleteURI: function PS_onDeleteURI(aURI) { - for each (let livemark in this._livemarks) { - livemark.updateURIVisitedStatus(aURI, false); - } + onDeleteURI(aURI) { + this._promiseLivemarksMap().then(livemarksMap => { + for (let livemark of livemarksMap.values()) { + livemark.updateURIVisitedStatus(aURI, false); + } + }); }, - onVisit: function PS_onVisit(aURI) { - for each (let livemark in this._livemarks) { - livemark.updateURIVisitedStatus(aURI, true); - } + onVisit(aURI) { + this._promiseLivemarksMap().then(livemarksMap => { + for (let livemark of livemarksMap.values()) { + livemark.updateURIVisitedStatus(aURI, true); + } + }); }, ////////////////////////////////////////////////////////////////////////////// @@ -520,17 +419,21 @@ LivemarkService.prototype = { */ function Livemark(aLivemarkInfo) { + this.id = aLivemarkInfo.id; + this.guid = aLivemarkInfo.guid; + this.feedURI = aLivemarkInfo.feedURI; + this.siteURI = aLivemarkInfo.siteURI || null; this.title = aLivemarkInfo.title; this.parentId = aLivemarkInfo.parentId; + this.parentGuid = aLivemarkInfo.parentGuid; this.index = aLivemarkInfo.index; + this.dateAdded = aLivemarkInfo.dateAdded; + this.lastModified = aLivemarkInfo.lastModified; this._status = Ci.mozILivemark.STATUS_READY; // Hash of resultObservers, hashed by container. this._resultObservers = new Map(); - // This keeps a list of the containers used as keys in the map, since - // it's not iterable. In future may use an iterable Map. - this._resultObserversList = []; // Sorted array of objects representing livemark children in the form // { uri, title, visited }. @@ -540,43 +443,8 @@ function Livemark(aLivemarkInfo) // the container itself. this._nodes = new Map(); - this._guid = ""; - this._dateAdded = 0; - this._lastModified = 0; this.loadGroup = null; - this.feedURI = null; - this.siteURI = null; this.expireTime = 0; - - if (aLivemarkInfo.id) { - // This request comes from the cache. - this.id = aLivemarkInfo.id; - this.guid = aLivemarkInfo.guid; - this.feedURI = aLivemarkInfo.feedURI; - this.siteURI = aLivemarkInfo.siteURI; - this.dateAdded = aLivemarkInfo.dateAdded; - this.lastModified = aLivemarkInfo.lastModified; - } - else { - // Create a new livemark. - this.id = PlacesUtils.bookmarks.createFolder(aLivemarkInfo.parentId, - aLivemarkInfo.title, - aLivemarkInfo.index, - aLivemarkInfo.guid); - this.writeFeedURI(aLivemarkInfo.feedURI); - if (aLivemarkInfo.siteURI) { - this.writeSiteURI(aLivemarkInfo.siteURI); - } - if (aLivemarkInfo.dateAdded) { - this.dateAdded = aLivemarkInfo.dateAdded; - PlacesUtils.bookmarks.setItemDateAdded(this.id, this.dateAdded); - } - // Last modified time must be the last change. - if (aLivemarkInfo.lastModified) { - this.lastModified = aLivemarkInfo.lastModified; - PlacesUtils.bookmarks.setItemLastModified(this.id, this.lastModified); - } - } } Livemark.prototype = { @@ -589,31 +457,15 @@ Livemark.prototype = { return this._status; }, - /** - * Sets an annotation on the bookmarks folder id representing the livemark. - * - * @param aAnnoName - * Name of the annotation. - * @param aValue - * Value of the annotation. - * @return The annotation value. - * @throws If the folder is invalid. - */ - _setAnno: function LM__setAnno(aAnnoName, aValue) - { + writeFeedURI(aFeedURI) { PlacesUtils.annotations - .setItemAnnotation(this.id, aAnnoName, aValue, 0, - PlacesUtils.annotations.EXPIRE_NEVER); - }, - - writeFeedURI: function LM_writeFeedURI(aFeedURI) - { - this._setAnno(PlacesUtils.LMANNO_FEEDURI, aFeedURI.spec); + .setItemAnnotation(this.id, PlacesUtils.LMANNO_FEEDURI, + aFeedURI.spec, + 0, PlacesUtils.annotations.EXPIRE_NEVER); this.feedURI = aFeedURI; }, - writeSiteURI: function LM_writeSiteURI(aSiteURI) - { + writeSiteURI(aSiteURI) { if (!aSiteURI) { PlacesUtils.annotations.removeItemAnnotation(this.id, PlacesUtils.LMANNO_SITEURI) @@ -622,27 +474,23 @@ Livemark.prototype = { } // Security check the site URI against the feed URI principal. + let secMan = Services.scriptSecurityManager; let feedPrincipal = secMan.getSimpleCodebasePrincipal(this.feedURI); try { - secMan.checkLoadURIWithPrincipal(feedPrincipal, aSiteURI, SEC_FLAGS); + secMan.checkLoadURIWithPrincipal(feedPrincipal, aSiteURI, + Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL); } catch (ex) { return; } - this._setAnno(PlacesUtils.LMANNO_SITEURI, aSiteURI.spec) + PlacesUtils.annotations + .setItemAnnotation(this.id, PlacesUtils.LMANNO_SITEURI, + aSiteURI.spec, + 0, PlacesUtils.annotations.EXPIRE_NEVER); this.siteURI = aSiteURI; }, - set guid(aGUID) this._guid = aGUID, - get guid() this._guid, - - set dateAdded(aDateAdded) this._dateAdded = aDateAdded, - get dateAdded() this._dateAdded, - - set lastModified(aLastModified) this._lastModified = aLastModified, - get lastModified() this._lastModified, - /** * Tries to updates the livemark if needed. * The update process is asynchronous. @@ -651,8 +499,7 @@ Livemark.prototype = { * If true will try to update the livemark even if its contents have * not yet expired. */ - updateChildren: function LM_updateChildren(aForceUpdate) - { + updateChildren(aForceUpdate) { // Check if the livemark is already updating. if (this.status == Ci.mozILivemark.STATUS_LOADING) return; @@ -674,17 +521,11 @@ Livemark.prototype = { // cancel the channel. let loadgroup = Cc["@mozilla.org/network/load-group;1"]. createInstance(Ci.nsILoadGroup); - let feedPrincipal = - secMan.getNoAppCodebasePrincipal(this.feedURI); - let channel = NetUtil.newChannel2(this.feedURI.spec, - null, - null, - null, // aLoadingNode - feedPrincipal, - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_DATAREQUEST). - QueryInterface(Ci.nsIHttpChannel); + let channel = NetUtil.newChannel({ + uri: this.feedURI.spec, + loadingPrincipal: Services.scriptSecurityManager.getNoAppCodebasePrincipal(this.feedURI), + contentPolicyType: Ci.nsIContentPolicy.TYPE_DATAREQUEST + }).QueryInterface(Ci.nsIHttpChannel); channel.loadGroup = loadgroup; channel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND | Ci.nsIRequest.LOAD_BYPASS_CACHE; @@ -703,47 +544,34 @@ Livemark.prototype = { } }, - reload: function LM_reload(aForceUpdate) - { + reload(aForceUpdate) { this.updateChildren(aForceUpdate); }, - remove: function LM_remove() { - PlacesUtils.bookmarks.removeItem(this.id); - }, - get children() this._children, set children(val) { this._children = val; // Discard the previous cached nodes, new ones should be generated. - for (let i = 0; i < this._resultObserversList.length; i++) { - let container = this._resultObserversList[i]; + for (let container of this._resultObservers.keys()) { this._nodes.delete(container); } // Update visited status for each entry. - for (let i = 0; i < this._children.length; i++) { - let child = this._children[i]; - asyncHistory.isURIVisited(child.uri, - (function(aURI, aIsVisited) { - this.updateURIVisitedStatus(aURI, aIsVisited); - }).bind(this)); + for (let child of this._children) { + asyncHistory.isURIVisited(child.uri, (aURI, aIsVisited) => { + this.updateURIVisitedStatus(aURI, aIsVisited); + }); } return this._children; }, - _isURIVisited: function LM__isURIVisited(aURI) { - for (let i = 0; i < this.children.length; i++) { - if (this.children[i].uri.equals(aURI)) { - return this.children[i].visited; - } - } + _isURIVisited(aURI) { + return this.children.some(child => child.uri.equals(aURI) && child.visited); }, - getNodesForContainer: function LM_getNodesForContainer(aContainerNode) - { + getNodesForContainer(aContainerNode) { if (this._nodes.has(aContainerNode)) { return this._nodes.get(aContainerNode); } @@ -751,8 +579,9 @@ Livemark.prototype = { let livemark = this; let nodes = []; let now = Date.now() * 1000; - for (let i = 0; i < this._children.length; i++) { - let child = this._children[i]; + for (let child of this.children) { + // Workaround for bug 449811. + let localChild = child; let node = { // The QueryInterface is needed cause aContainerNode is a jsval. // This is required to avoid issues with scriptable wrappers that would @@ -760,9 +589,9 @@ Livemark.prototype = { get parent() aContainerNode.QueryInterface(Ci.nsINavHistoryContainerResultNode), get parentResult() this.parent.parentResult, - get uri() child.uri.spec, + get uri() localChild.uri.spec, get type() Ci.nsINavHistoryResultNode.RESULT_TYPE_URI, - get title() child.title, + get title() localChild.title, get accessCount() Number(livemark._isURIVisited(NetUtil.newURI(this.uri))), get time() 0, @@ -770,8 +599,8 @@ Livemark.prototype = { get indentLevel() this.parent.indentLevel + 1, get bookmarkIndex() -1, get itemId() -1, - get dateAdded() now + i, - get lastModified() now + i, + get dateAdded() now, + get lastModified() now, get tags() PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(this.uri)).join(", "), QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryResultNode]) @@ -782,27 +611,17 @@ Livemark.prototype = { return nodes; }, - registerForUpdates: function LM_registerForUpdates(aContainerNode, - aResultObserver) - { + registerForUpdates(aContainerNode, aResultObserver) { this._resultObservers.set(aContainerNode, aResultObserver); - this._resultObserversList.push(aContainerNode); }, - unregisterForUpdates: function LM_unregisterForUpdates(aContainerNode) - { + unregisterForUpdates(aContainerNode) { this._resultObservers.delete(aContainerNode); - let index = this._resultObserversList.indexOf(aContainerNode); - this._resultObserversList.splice(index, 1); - this._nodes.delete(aContainerNode); }, - _invalidateRegisteredContainers: function LM__invalidateRegisteredContainers() - { - for (let i = 0; i < this._resultObserversList.length; i++) { - let container = this._resultObserversList[i]; - let observer = this._resultObservers.get(container); + _invalidateRegisteredContainers() { + for (let [ container, observer ] of this._resultObservers) { observer.invalidateContainer(container); } }, @@ -816,26 +635,24 @@ Livemark.prototype = { * @param aVisitedStatus * Whether the nodes should be set as visited. */ - updateURIVisitedStatus: - function LM_updateURIVisitedStatus(aURI, aVisitedStatus) - { - for (let i = 0; i < this.children.length; i++) { - if (!aURI || this.children[i].uri.equals(aURI)) { - this.children[i].visited = aVisitedStatus; + updateURIVisitedStatus(aURI, aVisitedStatus) { + for (let child of this.children) { + if (!aURI || child.uri.equals(aURI)) { + child.visited = aVisitedStatus; } } - for (let i = 0; i < this._resultObserversList.length; i++) { - let container = this._resultObserversList[i]; - let observer = this._resultObservers.get(container); + for (let [ container, observer ] of this._resultObservers) { if (this._nodes.has(container)) { let nodes = this._nodes.get(container); - for (let j = 0; j < nodes.length; j++) { - let node = nodes[j]; + for (let node of nodes) { + // Workaround for bug 449811. + localObserver = observer; + localNode = node; if (!aURI || node.uri == aURI.spec) { - Services.tm.mainThread.dispatch((function () { - observer.nodeHistoryDetailsChanged(node, 0, aVisitedStatus); - }).bind(this), Ci.nsIThread.DISPATCH_NORMAL); + Services.tm.mainThread.dispatch(() => { + localObserver.nodeHistoryDetailsChanged(localNode, 0, aVisitedStatus); + }, Ci.nsIThread.DISPATCH_NORMAL); } } } @@ -846,21 +663,16 @@ Livemark.prototype = { * Terminates the livemark entry, cancelling any ongoing load. * Must be invoked before destroying the entry. */ - terminate: function LM_terminate() - { + terminate() { // Avoid handling any updateChildren request from now on. this._terminated = true; - // Clear the list before aborting, since abort() would try to set the - // status and notify about it, but that's not really useful at this point. - this._resultObserversList = []; this.abort(); }, /** * Aborts the livemark loading if needed. */ - abort: function LM_abort() - { + abort() { this.status = Ci.mozILivemark.STATUS_FAILED; if (this.loadGroup) { this.loadGroup.cancel(Cr.NS_BINDING_ABORTED); @@ -882,8 +694,7 @@ Livemark.prototype = { * @param aLivemark * The Livemark that is loading. */ -function LivemarkLoadListener(aLivemark) -{ +function LivemarkLoadListener(aLivemark) { this._livemark = aLivemark; this._processor = null; this._isAborted = false; @@ -891,8 +702,7 @@ function LivemarkLoadListener(aLivemark) } LivemarkLoadListener.prototype = { - abort: function LLL_abort(aException) - { + abort(aException) { if (!this._isAborted) { this._isAborted = true; this._livemark.abort(); @@ -901,8 +711,7 @@ LivemarkLoadListener.prototype = { }, // nsIFeedResultListener - handleResult: function LLL_handleResult(aResult) - { + handleResult(aResult) { if (this._isAborted) { return; } @@ -910,7 +719,8 @@ LivemarkLoadListener.prototype = { try { // We need this to make sure the item links are safe let feedPrincipal = - secMan.getSimpleCodebasePrincipal(this._livemark.feedURI); + Services.scriptSecurityManager + .getSimpleCodebasePrincipal(this._livemark.feedURI); // Enforce well-formedness because the existing code does if (!aResult || !aResult.doc || aResult.bozo) { @@ -934,7 +744,9 @@ LivemarkLoadListener.prototype = { } try { - secMan.checkLoadURIWithPrincipal(feedPrincipal, uri, SEC_FLAGS); + Services.scriptSecurityManager + .checkLoadURIWithPrincipal(feedPrincipal, uri, + Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL); } catch(ex) { continue; @@ -955,20 +767,16 @@ LivemarkLoadListener.prototype = { } }, - onDataAvailable: function LLL_onDataAvailable(aRequest, aContext, - aInputStream, aSourceOffset, - aCount) - { + onDataAvailable(aRequest, aContext, aInputStream, aSourceOffset, aCount) { if (this._processor) { this._processor.onDataAvailable(aRequest, aContext, aInputStream, aSourceOffset, aCount); } }, - onStartRequest: function LLL_onStartRequest(aRequest, aContext) - { + onStartRequest(aRequest, aContext) { if (this._isAborted) { - throw Cr.NS_ERROR_UNEXPECTED; + throw new Components.Exception("", Cr.NS_ERROR_UNEXPECTED); } let channel = aRequest.QueryInterface(Ci.nsIChannel); @@ -986,8 +794,7 @@ LivemarkLoadListener.prototype = { } }, - onStopRequest: function LLL_onStopRequest(aRequest, aContext, aStatus) - { + onStopRequest(aRequest, aContext, aStatus) { if (!Components.isSuccessCode(aStatus)) { this.abort(); return; @@ -1029,14 +836,12 @@ LivemarkLoadListener.prototype = { } }, - _setResourceTTL: function LLL__setResourceTTL(aMilliseconds) - { + _setResourceTTL(aMilliseconds) { this._livemark.expireTime = Date.now() + aMilliseconds; }, // nsIInterfaceRequestor - getInterface: function LLL_getInterface(aIID) - { + getInterface(aIID) { return this.QueryInterface(aIID); }, diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_update.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_update.js index 187a2a63dec4..c33c9b8ccf0b 100644 --- a/toolkit/components/places/tests/bookmarks/test_bookmarks_update.js +++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_update.js @@ -65,9 +65,9 @@ add_task(function* invalid_input_throws() { Assert.throws(() => PlacesUtils.bookmarks.update({ url: "te st" }), /Invalid value for property 'url'/); - Assert.throws(() => PlacesUtils.bookmarks.insert({ title: -1 }), + Assert.throws(() => PlacesUtils.bookmarks.update({ title: -1 }), /Invalid value for property 'title'/); - Assert.throws(() => PlacesUtils.bookmarks.insert({ title: undefined }), + Assert.throws(() => PlacesUtils.bookmarks.update({ title: {} }), /Invalid value for property 'title'/); Assert.throws(() => PlacesUtils.bookmarks.update({ guid: "123456789012" }), diff --git a/toolkit/components/places/tests/unit/test_bookmarks_html.js b/toolkit/components/places/tests/unit/test_bookmarks_html.js index e55f580b09ea..ef76bdf1e43b 100644 --- a/toolkit/components/places/tests/unit/test_bookmarks_html.js +++ b/toolkit/components/places/tests/unit/test_bookmarks_html.js @@ -45,7 +45,8 @@ let test_bookmarks = { keyword: "test", sidebar: true, postData: "hidden1%3Dbar&text1%3D%25s", - charset: "ISO-8859-1" + charset: "ISO-8859-1", + url: "http://test/post" } ] } @@ -72,8 +73,6 @@ let gBookmarksFileOld; // Places bookmarks.html file pointer. let gBookmarksFileNew; -Cu.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); - function run_test() { run_next_test(); @@ -138,10 +137,10 @@ add_task(function* test_emptytitle_export() yield PlacesTestUtils.promiseAsyncUpdates(); const NOTITLE_URL = "http://notitle.mozilla.org/"; - let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, - NetUtil.newURI(NOTITLE_URL), - PlacesUtils.bookmarks.DEFAULT_INDEX, - ""); + let bookmark = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: NOTITLE_URL + }); test_bookmarks.unfiled.push({ title: "", url: NOTITLE_URL }); yield BookmarkHTMLUtils.exportToFile(gBookmarksFileNew); @@ -154,7 +153,13 @@ add_task(function* test_emptytitle_export() // Cleanup. test_bookmarks.unfiled.pop(); - PlacesUtils.bookmarks.removeItem(id); + // HTML imports don't restore GUIDs yet. + let reimportedBookmark = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX + }); + Assert.equal(reimportedBookmark.url.href, bookmark.url.href); + yield PlacesUtils.bookmarks.remove(reimportedBookmark); yield BookmarkHTMLUtils.exportToFile(gBookmarksFileNew); yield PlacesTestUtils.promiseAsyncUpdates(); @@ -178,24 +183,29 @@ add_task(function* test_import_chromefavicon() const CHROME_FAVICON_URI = NetUtil.newURI("chrome://global/skin/icons/information-16.png"); const CHROME_FAVICON_URI_2 = NetUtil.newURI("chrome://global/skin/icons/error-16.png"); + do_print("Importing from html"); yield BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true); yield PlacesTestUtils.promiseAsyncUpdates(); - let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, - PAGE_URI, - PlacesUtils.bookmarks.DEFAULT_INDEX, - "Test"); - let deferred = Promise.defer(); - PlacesUtils.favicons.setAndFetchFaviconForPage( - PAGE_URI, CHROME_FAVICON_URI, true, - PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, - deferred.resolve); - yield deferred.promise; + do_print("Insert bookmark"); + let bookmark = yield PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: PAGE_URI, + title: "Test" + }); - deferred = Promise.defer(); - PlacesUtils.favicons.getFaviconDataForPage(PAGE_URI, - function (aURI, aDataLen, aData, aMimeType) deferred.resolve(aData)); - let data = yield deferred.promise; + do_print("Set favicon"); + yield new Promise(resolve => { + PlacesUtils.favicons.setAndFetchFaviconForPage( + PAGE_URI, CHROME_FAVICON_URI, true, + PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + resolve); + }); + + let data = yield new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + PAGE_URI, (uri, dataLen, data, mimeType) => resolve(data)); + }); let base64Icon = "data:image/png;base64," + base64EncodeString(String.fromCharCode.apply(String, data)); @@ -203,26 +213,35 @@ add_task(function* test_import_chromefavicon() test_bookmarks.unfiled.push( { title: "Test", url: PAGE_URI.spec, icon: base64Icon }); + do_print("Export to html"); yield BookmarkHTMLUtils.exportToFile(gBookmarksFileNew); yield PlacesTestUtils.promiseAsyncUpdates(); + do_print("Set favicon"); // Change the favicon to check it's really imported again later. - deferred = Promise.defer(); - PlacesUtils.favicons.setAndFetchFaviconForPage( - PAGE_URI, CHROME_FAVICON_URI_2, true, - PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, - deferred.resolve); - yield deferred.promise; + yield new Promise(resolve => { + PlacesUtils.favicons.setAndFetchFaviconForPage( + PAGE_URI, CHROME_FAVICON_URI_2, true, + PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + resolve); + }); + do_print("import from html"); yield PlacesUtils.bookmarks.eraseEverything(); - yield BookmarkHTMLUtils.importFromFile(gBookmarksFileNew, true); yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("Test imported bookmarks"); yield testImportedBookmarks(); // Cleanup. test_bookmarks.unfiled.pop(); - PlacesUtils.bookmarks.removeItem(id); + // HTML imports don't restore GUIDs yet. + let reimportedBookmark = yield PlacesUtils.bookmarks.fetch({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX + }); + yield PlacesUtils.bookmarks.remove(reimportedBookmark); yield BookmarkHTMLUtils.exportToFile(gBookmarksFileNew); yield PlacesTestUtils.promiseAsyncUpdates(); diff --git a/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js b/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js index c302615f954b..c034a6fd084e 100644 --- a/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js +++ b/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js @@ -101,14 +101,22 @@ let database_check = Task.async(function* () { root = PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root; Assert.equal(root.childCount, 3); - let livemarkNode = root.getChild(1); - Assert.equal("Latest Headlines", livemarkNode.title); + // For now some promises are resolved later, so we can't guarantee an order. + let foundLivemark = false; + for (let i = 0; i < root.childCount; ++i) { + let node = root.getChild(i); + if (node.title == "Latest Headlines") { + foundLivemark = true; + Assert.equal("Latest Headlines", node.title); - let livemark = yield PlacesUtils.livemarks.getLivemark({ id: livemarkNode.itemId }); - Assert.equal("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/", - livemark.siteURI.spec); - Assert.equal("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml", - livemark.feedURI.spec); + let livemark = yield PlacesUtils.livemarks.getLivemark({ guid: node.bookmarkGuid }); + Assert.equal("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/", + livemark.siteURI.spec); + Assert.equal("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml", + livemark.feedURI.spec); + } + } + Assert.ok(foundLivemark); // cleanup root.containerOpen = false; diff --git a/toolkit/components/places/tests/unit/test_bug636917_isLivemark.js b/toolkit/components/places/tests/unit/test_bug636917_isLivemark.js index 62536ede97ae..a7ad1257adef 100644 --- a/toolkit/components/places/tests/unit/test_bug636917_isLivemark.js +++ b/toolkit/components/places/tests/unit/test_bug636917_isLivemark.js @@ -1,40 +1,35 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ +// Test that asking for a livemark in a annotationChanged notification works. +add_task(function* () { + let annoPromise = new Promise(resolve => { + let annoObserver = { + onItemAnnotationSet(id, name) { + if (name == PlacesUtils.LMANNO_FEEDURI) { + PlacesUtils.annotations.removeObserver(this); + resolve(); + } + }, + onItemAnnotationRemoved() {}, + onPageAnnotationSet() {}, + onPageAnnotationRemoved() {}, + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIAnnotationObserver + ]), + }; + PlacesUtils.annotations.addObserver(annoObserver, false); + }); -// Test that asking for isLivemark in a annotationChanged notification -// correctly returns true. -function run_test() -{ - do_test_pending(); - let annoObserver = { - onItemAnnotationSet: - function AO_onItemAnnotationSet(aItemId, aAnnotationName) - { - if (aAnnotationName == PlacesUtils.LMANNO_FEEDURI) { - PlacesUtils.annotations.removeObserver(this); - PlacesUtils.livemarks.getLivemark({ id: aItemId }) - .then(aLivemark => { - PlacesUtils.bookmarks.removeItem(aItemId); - do_test_finished(); - }, do_throw); - } - }, - - onItemAnnotationRemoved: function () {}, - onPageAnnotationSet: function() {}, - onPageAnnotationRemoved: function() {}, - QueryInterface: XPCOMUtils.generateQI([ - Ci.nsIAnnotationObserver - ]), - } - PlacesUtils.annotations.addObserver(annoObserver, false); - PlacesUtils.livemarks.addLivemark( + let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "livemark title" - , parentId: PlacesUtils.unfiledBookmarksFolderId + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , index: PlacesUtils.bookmarks.DEFAULT_INDEX , siteURI: uri("http://example.com/") , feedURI: uri("http://example.com/rdf") - } - ).then(null, do_throw); -} + }); + + yield annoPromise; + + livemark = yield PlacesUtils.livemarks.getLivemark({ guid: livemark.guid }); + Assert.ok(livemark); + yield PlacesUtils.livemarks.removeLivemark({ guid: livemark.guid }); +}); diff --git a/toolkit/components/places/tests/unit/test_mozIAsyncLivemarks.js b/toolkit/components/places/tests/unit/test_mozIAsyncLivemarks.js index 8e4e43e579f1..0581744de2c3 100644 --- a/toolkit/components/places/tests/unit/test_mozIAsyncLivemarks.js +++ b/toolkit/components/places/tests/unit/test_mozIAsyncLivemarks.js @@ -6,20 +6,16 @@ const FEED_URI = NetUtil.newURI("http://feed.rss/"); const SITE_URI = NetUtil.newURI("http://site.org/"); - -add_task(function test_addLivemark_noArguments_throws() -{ +add_task(function* test_addLivemark_noArguments_throws() { try { yield PlacesUtils.livemarks.addLivemark(); do_throw("Invoking addLivemark with no arguments should throw"); } catch (ex) { - // The error is actually generated by XPConnect. do_check_eq(ex.result, Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS); } }); -add_task(function test_addLivemark_emptyObject_throws() -{ +add_task(function* test_addLivemark_emptyObject_throws() { try { yield PlacesUtils.livemarks.addLivemark({}); do_throw("Invoking addLivemark with empty object should throw"); @@ -28,8 +24,7 @@ add_task(function test_addLivemark_emptyObject_throws() } }); -add_task(function test_addLivemark_badParentId_throws() -{ +add_task(function* test_addLivemark_badParentId_throws() { try { yield PlacesUtils.livemarks.addLivemark({ parentId: "test" }); do_throw("Invoking addLivemark with a bad parent id should throw"); @@ -38,8 +33,7 @@ add_task(function test_addLivemark_badParentId_throws() } }); -add_task(function test_addLivemark_invalidParentId_throws() -{ +add_task(function* test_addLivemark_invalidParentId_throws() { try { yield PlacesUtils.livemarks.addLivemark({ parentId: -2 }); do_throw("Invoking addLivemark with an invalid parent id should throw"); @@ -48,8 +42,7 @@ add_task(function test_addLivemark_invalidParentId_throws() } }); -add_task(function test_addLivemark_noIndex_throws() -{ +add_task(function* test_addLivemark_noIndex_throws() { try { yield PlacesUtils.livemarks.addLivemark({ parentId: PlacesUtils.unfiledBookmarksFolderId }); @@ -59,8 +52,7 @@ add_task(function test_addLivemark_noIndex_throws() } }); -add_task(function test_addLivemark_badIndex_throws() -{ +add_task(function* test_addLivemark_badIndex_throws() { try { yield PlacesUtils.livemarks.addLivemark( { parentId: PlacesUtils.unfiledBookmarksFolderId @@ -71,8 +63,7 @@ add_task(function test_addLivemark_badIndex_throws() } }); -add_task(function test_addLivemark_invalidIndex_throws() -{ +add_task(function* test_addLivemark_invalidIndex_throws() { try { yield PlacesUtils.livemarks.addLivemark( { parentId: PlacesUtils.unfiledBookmarksFolderId @@ -84,24 +75,20 @@ add_task(function test_addLivemark_invalidIndex_throws() } }); -add_task(function test_addLivemark_noFeedURI_throws() -{ +add_task(function* test_addLivemark_noFeedURI_throws() { try { yield PlacesUtils.livemarks.addLivemark( - { parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX }); + { parentGuid: PlacesUtils.bookmarks.unfiledGuid }); do_throw("Invoking addLivemark with no feedURI should throw"); } catch (ex) { do_check_eq(ex.result, Cr.NS_ERROR_INVALID_ARG); } }); -add_task(function test_addLivemark_badFeedURI_throws() -{ +add_task(function* test_addLivemark_badFeedURI_throws() { try { yield PlacesUtils.livemarks.addLivemark( - { parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + { parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: "test" }); do_throw("Invoking addLivemark with a bad feedURI should throw"); } catch (ex) { @@ -109,12 +96,10 @@ add_task(function test_addLivemark_badFeedURI_throws() } }); -add_task(function test_addLivemark_badSiteURI_throws() -{ +add_task(function* test_addLivemark_badSiteURI_throws() { try { yield PlacesUtils.livemarks.addLivemark( - { parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + { parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI , siteURI: "test" }); do_throw("Invoking addLivemark with a bad siteURI should throw"); @@ -123,12 +108,10 @@ add_task(function test_addLivemark_badSiteURI_throws() } }); -add_task(function test_addLivemark_badGuid_throws() -{ +add_task(function* test_addLivemark_badGuid_throws() { try { yield PlacesUtils.livemarks.addLivemark( - { parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + { parentGuid: PlacesUtils.bookmarks.unfileGuid , feedURI: FEED_URI , guid: "123456" }); do_throw("Invoking addLivemark with a bad guid should throw"); @@ -137,23 +120,7 @@ add_task(function test_addLivemark_badGuid_throws() } }); -add_task(function test_addLivemark_badCallback_throws() -{ - try { - yield PlacesUtils.livemarks.addLivemark( - { parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX - , feedURI: FEED_URI - }, "test"); - do_throw("Invoking addLivemark with a bad callback should throw"); - } catch (ex) { - // The error is actually generated by XPConnect. - do_check_eq(ex.result, Cr.NS_ERROR_XPC_BAD_CONVERT_JS); - } -}); - -add_task(function test_addLivemark_noCallback_succeeds() -{ +add_task(function* test_addLivemark_parentId_succeeds() { let onItemAddedCalled = false; PlacesUtils.bookmarks.addObserver({ __proto__: NavBookmarkObserver.prototype, @@ -172,37 +139,35 @@ add_task(function test_addLivemark_noCallback_succeeds() yield PlacesUtils.livemarks.addLivemark( { title: "test" , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX , feedURI: FEED_URI }); do_check_true(onItemAddedCalled); }); -add_task(function test_addLivemark_noSiteURI_succeeds() -{ +add_task(function* test_addLivemark_noSiteURI_succeeds() { let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI }); do_check_true(livemark.id > 0); do_check_valid_places_guid(livemark.guid); do_check_eq(livemark.title, "test"); do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId); - do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id)); - do_check_eq(livemark.lastModified, PlacesUtils.bookmarks.getItemLastModified(livemark.id)); - do_check_eq(livemark.dateAdded, PlacesUtils.bookmarks.getItemDateAdded(livemark.id)); + do_check_eq(livemark.parentGuid, PlacesUtils.bookmarks.unfiledGuid); do_check_true(livemark.feedURI.equals(FEED_URI)); do_check_eq(livemark.siteURI, null); + do_check_true(livemark.lastModified > 0); + + let bookmark = yield PlacesUtils.bookmarks.fetch(livemark.guid); + do_check_eq(livemark.index, bookmark.index); + do_check_eq(livemark.dateAdded, bookmark.dateAdded * 1000); }); -add_task(function test_addLivemark_succeeds() -{ +add_task(function* test_addLivemark_succeeds() { let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI , siteURI: SITE_URI }); @@ -211,9 +176,7 @@ add_task(function test_addLivemark_succeeds() do_check_valid_places_guid(livemark.guid); do_check_eq(livemark.title, "test"); do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId); - do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id)); - do_check_eq(livemark.dateAdded, PlacesUtils.bookmarks.getItemDateAdded(livemark.id)); - do_check_eq(livemark.lastModified, PlacesUtils.bookmarks.getItemLastModified(livemark.id)); + do_check_eq(livemark.parentGuid, PlacesUtils.bookmarks.unfiledGuid); do_check_true(livemark.feedURI.equals(FEED_URI)); do_check_true(livemark.siteURI.equals(SITE_URI)); do_check_true(PlacesUtils.annotations @@ -224,13 +187,11 @@ add_task(function test_addLivemark_succeeds() PlacesUtils.LMANNO_SITEURI)); }); -add_task(function test_addLivemark_bogusid_succeeds() -{ +add_task(function* test_addLivemark_bogusid_succeeds() { let livemark = yield PlacesUtils.livemarks.addLivemark( { id: 100 // Should be ignored. , title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI , siteURI: SITE_URI }); @@ -238,84 +199,83 @@ add_task(function test_addLivemark_bogusid_succeeds() do_check_neq(livemark.id, 100); }); -add_task(function test_addLivemark_bogusParent_fails() -{ +add_task(function* test_addLivemark_bogusParentId_fails() { try { yield PlacesUtils.livemarks.addLivemark( { title: "test" , parentId: 187 - , index: PlacesUtils.bookmarks.DEFAULT_INDEX , feedURI: FEED_URI }); do_throw("Adding a livemark with a bogus parent should fail"); } catch(ex) {} }); -add_task(function test_addLivemark_intoLivemark_fails() -{ +add_task(function* test_addLivemark_bogusParentGuid_fails() { + try { + yield PlacesUtils.livemarks.addLivemark( + { title: "test" + , parentGuid: "123456789012" + , feedURI: FEED_URI + }); + do_throw("Adding a livemark with a bogus parent should fail"); + } catch(ex) {} +}) + +add_task(function* test_addLivemark_intoLivemark_fails() { let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI }); - do_check_true(Boolean(livemark)); try { yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: livemark.id - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: livemark.guid , feedURI: FEED_URI }); do_throw("Adding a livemark into a livemark should fail"); - } catch(ex) {} + } catch(ex) { + do_check_eq(ex.result, Cr.NS_ERROR_INVALID_ARG); + } }); -add_task(function test_addLivemark_forceGuid_succeeds() -{ - let checkLivemark = aLivemark => { - do_check_eq(aLivemark.guid, "1234567890AB"); - do_check_guid_for_bookmark(aLivemark.id, "1234567890AB"); - }; - +add_task(function* test_addLivemark_forceGuid_succeeds() { let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI , guid: "1234567890AB" }); - checkLivemark(livemark); + do_check_eq(livemark.guid, "1234567890AB"); + do_check_guid_for_bookmark(livemark.id, "1234567890AB"); }); add_task(function* test_addLivemark_dateAdded_succeeds() { let dateAdded = new Date("2013-03-01T01:10:00") * 1000; let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI , dateAdded }); do_check_eq(livemark.dateAdded, dateAdded); }); -add_task(function test_addLivemark_lastModified_succeeds() -{ +add_task(function* test_addLivemark_lastModified_succeeds() { let now = Date.now() * 1000; let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI , lastModified: now }); - do_check_eq(livemark.lastModified, now); + do_check_eq(livemark.dateAdded, now); + // lastModified is updated when annotations are added to the livemark. + do_check_true(livemark.lastModified >= now); }); -add_task(function test_removeLivemark_emptyObject_throws() -{ +add_task(function* test_removeLivemark_emptyObject_throws() { try { yield PlacesUtils.livemarks.removeLivemark({}); do_throw("Invoking removeLivemark with empty object should throw"); @@ -324,8 +284,7 @@ add_task(function test_removeLivemark_emptyObject_throws() } }); -add_task(function test_removeLivemark_noValidId_throws() -{ +add_task(function* test_removeLivemark_noValidId_throws() { try { yield PlacesUtils.livemarks.removeLivemark({ id: -10, guid: "test"}); do_throw("Invoking removeLivemark with no valid id should throw"); @@ -334,8 +293,7 @@ add_task(function test_removeLivemark_noValidId_throws() } }); -add_task(function test_removeLivemark_nonExistent_fails() -{ +add_task(function* test_removeLivemark_nonExistent_fails() { try { yield PlacesUtils.livemarks.removeLivemark({ id: 1337 }); do_throw("Removing a non-existent livemark should fail"); @@ -344,41 +302,36 @@ add_task(function test_removeLivemark_nonExistent_fails() } }); -add_task(function test_removeLivemark_guid_succeeds() -{ +add_task(function* test_removeLivemark_guid_succeeds() { let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI , guid: "234567890ABC" }); - do_check_eq(livemark.guid, "234567890ABC"); yield PlacesUtils.livemarks.removeLivemark({ id: 789, guid: "234567890ABC" }); - do_check_eq(PlacesUtils.bookmarks.getItemIndex(livemark.id), -1); + do_check_eq((yield PlacesUtils.bookmarks.fetch("234567890ABC")), null); }); -add_task(function test_removeLivemark_id_succeeds() -{ +add_task(function* test_removeLivemark_id_succeeds() { let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI }); yield PlacesUtils.livemarks.removeLivemark({ id: livemark.id }); - do_check_eq(PlacesUtils.bookmarks.getItemIndex(livemark.id), -1); + + do_check_eq((yield PlacesUtils.bookmarks.fetch("234567890ABC")), null); }); -add_task(function test_getLivemark_emptyObject_throws() -{ +add_task(function* test_getLivemark_emptyObject_throws() { try { yield PlacesUtils.livemarks.getLivemark({}); do_throw("Invoking getLivemark with empty object should throw"); @@ -387,8 +340,7 @@ add_task(function test_getLivemark_emptyObject_throws() } }); -add_task(function test_getLivemark_noValidId_throws() -{ +add_task(function* test_getLivemark_noValidId_throws() { try { yield PlacesUtils.livemarks.getLivemark({ id: -10, guid: "test"}); do_throw("Invoking getLivemark with no valid id should throw"); @@ -397,30 +349,24 @@ add_task(function test_getLivemark_noValidId_throws() } }); -add_task(function test_getLivemark_nonExistentId_fails() -{ +add_task(function* test_getLivemark_nonExistentId_fails() { try { yield PlacesUtils.livemarks.getLivemark({ id: 1234 }); do_throw("getLivemark for a non existent id should fail"); - } - catch(ex) {} + } catch (ex) {} }); -add_task(function test_getLivemark_nonExistentGUID_fails() -{ +add_task(function* test_getLivemark_nonExistentGUID_fails() { try { yield PlacesUtils.livemarks.getLivemark({ guid: "34567890ABCD" }); do_throw("getLivemark for a non-existent guid should fail"); - } - catch(ex) {} + } catch (ex) {} }); -add_task(function test_getLivemark_guid_succeeds() -{ +add_task(function* test_getLivemark_guid_succeeds() { yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI , guid: "34567890ABCD" }); @@ -430,18 +376,19 @@ add_task(function test_getLivemark_guid_succeeds() do_check_eq(livemark.title, "test"); do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId); - do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id)); + do_check_eq(livemark.parentGuid, PlacesUtils.bookmarks.unfiledGuid); do_check_true(livemark.feedURI.equals(FEED_URI)); do_check_eq(livemark.siteURI, null); do_check_eq(livemark.guid, "34567890ABCD"); + + let bookmark = yield PlacesUtils.bookmarks.fetch("34567890ABCD"); + do_check_eq(livemark.index, bookmark.index); }); -add_task(function test_getLivemark_id_succeeds() -{ +add_task(function* test_getLivemark_id_succeeds() { let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI }); @@ -449,65 +396,87 @@ add_task(function test_getLivemark_id_succeeds() do_check_eq(livemark.title, "test"); do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId); - do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id)); + do_check_eq(livemark.parentGuid, PlacesUtils.bookmarks.unfiledGuid); do_check_true(livemark.feedURI.equals(FEED_URI)); do_check_eq(livemark.siteURI, null); do_check_guid_for_bookmark(livemark.id, livemark.guid); + + let bookmark = yield PlacesUtils.bookmarks.fetch(livemark.guid); + do_check_eq(livemark.index, bookmark.index); }); -add_task(function test_getLivemark_removeItem_contention() -{ +add_task(function* test_getLivemark_removeItem_contention() { + // do not yield. PlacesUtils.livemarks.addLivemark({ title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI }); - PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); - PlacesUtils.livemarks.addLivemark({ title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX - , feedURI: FEED_URI - }); - let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, - PlacesUtils.bookmarks.DEFAULT_INDEX); + yield PlacesUtils.bookmarks.eraseEverything(); + let livemark = yield PlacesUtils.livemarks.addLivemark( + { title: "test" + , parentGuid: PlacesUtils.bookmarks.unfiledGuid + , feedURI: FEED_URI + }); - let livemark = yield PlacesUtils.livemarks.getLivemark({ id: id }); + livemark = yield PlacesUtils.livemarks.getLivemark({ guid: livemark.guid }); do_check_eq(livemark.title, "test"); do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId); - do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id)); do_check_true(livemark.feedURI.equals(FEED_URI)); do_check_eq(livemark.siteURI, null); do_check_guid_for_bookmark(livemark.id, livemark.guid); }); -add_task(function test_title_change() -{ +add_task(function* test_title_change() { let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX - , feedURI: FEED_URI }); + , parentGuid: PlacesUtils.bookmarks.unfiledGuid + , feedURI: FEED_URI + }); - PlacesUtils.bookmarks.setItemTitle(livemark.id, "test2"); - do_check_eq(livemark.title, "test2"); + yield PlacesUtils.bookmarks.update({ guid: livemark.guid, + title: "test2" }); + // Poll for the title change. + while (true) { + let lm = yield PlacesUtils.livemarks.getLivemark({ guid: livemark.guid }); + if (lm.title == "test2") + break; + yield new Promise(resolve => do_timeout(resolve, 100)); + } }); -add_task(function test_livemark_move() -{ +add_task(function* test_livemark_move() { let livemark = yield PlacesUtils.livemarks.addLivemark( { title: "test" - , parentId: PlacesUtils.unfiledBookmarksFolderId - , index: PlacesUtils.bookmarks.DEFAULT_INDEX + , parentGuid: PlacesUtils.bookmarks.unfiledGuid , feedURI: FEED_URI } ); - PlacesUtils.bookmarks.moveItem(livemark.id, - PlacesUtils.toolbarFolderId, - PlacesUtils.bookmarks.DEFAULT_INDEX); - do_check_eq(livemark.parentId, PlacesUtils.toolbarFolderId); - do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id)); + yield PlacesUtils.bookmarks.update({ guid: livemark.guid, + parentGuid: PlacesUtils.bookmarks.toolbarGuid, + index: PlacesUtils.bookmarks.DEFAULT_INDEX }); + // Poll for the parent change. + while (true) { + let lm = yield PlacesUtils.livemarks.getLivemark({ guid: livemark.guid }); + if (lm.parentGuid == PlacesUtils.bookmarks.toolbarGuid) + break; + yield new Promise(resolve => do_timeout(resolve, 100)); + } }); -function run_test() { - run_next_test(); -} +add_task(function* test_livemark_removed() { + let livemark = yield PlacesUtils.livemarks.addLivemark( + { title: "test" + , parentGuid: PlacesUtils.bookmarks.unfiledGuid + , feedURI: FEED_URI } ); + + yield PlacesUtils.bookmarks.remove(livemark.guid); + // Poll for the livemark removal. + while (true) { + try { + yield PlacesUtils.livemarks.getLivemark({ guid: livemark.guid }); + } catch (ex) { + break; + } + yield new Promise(resolve => do_timeout(resolve, 100)); + } +});