--- a/toolkit/components/places/nsLivemarkService.js
+++ b/toolkit/components/places/nsLivemarkService.js
@@ -46,44 +46,16 @@ XPCOMUtils.defineLazyGetter(this, "CACHE
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",
- async function() {
- let livemarksMap = new Map();
- let conn = await PlacesUtils.promiseDBConnection();
- let rows = await 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("parentId"),
- parentGuid: row.getResultByName("parentGuid"),
- index: row.getResultByName("index"),
- 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) {
@@ -104,21 +76,53 @@ function toDate(time) {
// LivemarkService
function LivemarkService() {
// Cleanup on shutdown.
Services.obs.addObserver(this, PlacesUtils.TOPIC_SHUTDOWN, true);
// Observe bookmarks but don't init the service just for that.
PlacesUtils.bookmarks.addObserver(this, true);
+
+ this._livemarksMap = null;
+ this._promiseLivemarksMapReady = Promise.resolve();
}
LivemarkService.prototype = {
- // This is just an helper for code readability.
- _promiseLivemarksMap: () => gLivemarksCachePromised,
+ _withLivemarksMap(func) {
+ let promise = this._promiseLivemarksMapReady.then(async () => {
+ if (!this._livemarksMap) {
+ this._livemarksMap = new Map();
+ let conn = await PlacesUtils.promiseDBConnection();
+ let rows = await 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("parentId"),
+ parentGuid: row.getResultByName("parentGuid"),
+ index: row.getResultByName("index"),
+ dateAdded: row.getResultByName("dateAdded"),
+ lastModified: row.getResultByName("lastModified"),
+ feedURI: NetUtil.newURI(row.getResultByName("feedURI")),
+ siteURI: siteURI ? NetUtil.newURI(siteURI) : null
+ });
+ this._livemarksMap.set(livemark.guid, livemark);
+ }
+ }
+ return func(this._livemarksMap);
+ });
+ this._promiseLivemarksMapReady = promise.then(() => {}, Cu.reportError);
+ return promise;
+ },
_reloading: false,
_startReloadTimer(livemarksMap, forceUpdate, reloaded) {
if (this._reloadTimer) {
this._reloadTimer.cancel();
} else {
this._reloadTimer = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
@@ -139,28 +143,17 @@ LivemarkService.prototype = {
this._reloading = false;
}, RELOAD_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
},
// nsIObserver
observe(aSubject, aTopic, aData) {
if (aTopic == PlacesUtils.TOPIC_SHUTDOWN) {
- if (this._reloadTimer) {
- this._reloading = false;
- this._reloadTimer.cancel();
- delete this._reloadTimer;
- }
-
- // Stop any ongoing network fetch.
- this._promiseLivemarksMap().then(livemarksMap => {
- for (let livemark of livemarksMap.values()) {
- livemark.terminate();
- }
- });
+ this.invalidateCachedLivemarks().catch(Cu.reportError);
}
},
// mozIAsyncLivemarks
addLivemark(aLivemarkInfo) {
if (!aLivemarkInfo) {
throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
@@ -174,22 +167,20 @@ LivemarkService.prototype = {
(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 new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
}
- return (async () => {
+ return this._withLivemarksMap(async livemarksMap => {
if (!aLivemarkInfo.parentGuid)
aLivemarkInfo.parentGuid = await PlacesUtils.promiseItemGuid(aLivemarkInfo.parentId);
- let livemarksMap = await this._promiseLivemarksMap();
-
// Disallow adding a livemark inside another livemark.
if (livemarksMap.has(aLivemarkInfo.parentGuid)) {
throw new Components.Exception("Cannot create a livemark inside a livemark", Cr.NS_ERROR_INVALID_ARG);
}
// Create a new livemark.
let folder = await PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
@@ -227,56 +218,55 @@ LivemarkService.prototype = {
lastModified: toDate(aLivemarkInfo.lastModified),
source: aLivemarkInfo.source });
livemark.lastModified = aLivemarkInfo.lastModified;
}
livemarksMap.set(folder.guid, livemark);
return livemark;
- })();
+ });
},
removeLivemark(aLivemarkInfo) {
if (!aLivemarkInfo) {
throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
}
// Accept either a guid or an id.
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);
}
- return (async () => {
+ return this._withLivemarksMap(async livemarksMap => {
if (!aLivemarkInfo.guid)
aLivemarkInfo.guid = await PlacesUtils.promiseItemGuid(aLivemarkInfo.id);
- let livemarksMap = await this._promiseLivemarksMap();
if (!livemarksMap.has(aLivemarkInfo.guid))
throw new Components.Exception("Invalid livemark", Cr.NS_ERROR_INVALID_ARG);
await PlacesUtils.bookmarks.remove(aLivemarkInfo.guid,
{ source: aLivemarkInfo.source });
- })();
+ });
},
reloadLivemarks(aForceUpdate) {
// Check if there's a currently running reload, to save some useless work.
let notWorthRestarting =
this._forceUpdate || // We're already forceUpdating.
!aForceUpdate; // The caller didn't request a forced update.
if (this._reloading && notWorthRestarting) {
// Ignore this call.
return;
}
- this._promiseLivemarksMap().then(livemarksMap => {
+ this._withLivemarksMap(livemarksMap => {
this._forceUpdate = !!aForceUpdate;
// Livemarks reloads happen on a timer for performance reasons.
this._startReloadTimer(livemarksMap, this._forceUpdate, new Set());
});
},
getLivemark(aLivemarkInfo) {
if (!aLivemarkInfo) {
@@ -286,103 +276,127 @@ LivemarkService.prototype = {
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);
}
- return (async () => {
+ return this._withLivemarksMap(async livemarksMap => {
if (!aLivemarkInfo.guid)
aLivemarkInfo.guid = await PlacesUtils.promiseItemGuid(aLivemarkInfo.id);
- let livemarksMap = await this._promiseLivemarksMap();
if (!livemarksMap.has(aLivemarkInfo.guid))
throw new Components.Exception("Invalid livemark", Cr.NS_ERROR_INVALID_ARG);
return livemarksMap.get(aLivemarkInfo.guid);
- })();
+ });
+ },
+
+ invalidateCachedLivemarks() {
+ if (this._reloadTimer) {
+ this._reloading = false;
+ this._reloadTimer.cancel();
+ delete this._reloadTimer;
+ }
+ let promise = this._promiseLivemarksMapReady.then(() => {
+ let livemarksMap = this._livemarksMap;
+ this._livemarksMap = null;
+ if (livemarksMap) {
+ // Stop any ongoing network fetch.
+ for (let livemark of livemarksMap.values()) {
+ livemark.terminate();
+ }
+ }
+ });
+ this._promiseLivemarksMapReady = promise.catch(Cu.reportError);
+ return promise;
},
// nsINavBookmarkObserver
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onItemVisited() {},
onItemAdded() {},
onItemChanged(id, property, isAnno, value, lastModified, itemType, parentId,
guid, parentGuid) {
if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
return;
- this._promiseLivemarksMap().then(livemarksMap => {
+ if (isAnno && (property == PlacesUtils.LMANNO_FEEDURI ||
+ property == PlacesUtils.LMANNO_SITEURI)) {
+ return;
+ }
+
+ this._withLivemarksMap(livemarksMap => {
if (livemarksMap.has(guid)) {
let livemark = livemarksMap.get(guid);
if (property == "title") {
livemark.title = value;
}
livemark.lastModified = lastModified;
}
});
},
onItemMoved(id, parentId, oldIndex, newParentId, newIndex, itemType, guid,
oldParentGuid, newParentGuid) {
if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
return;
- this._promiseLivemarksMap().then(livemarksMap => {
+ this._withLivemarksMap(livemarksMap => {
if (livemarksMap.has(guid)) {
let livemark = livemarksMap.get(guid);
livemark.parentId = newParentId;
livemark.parentGuid = newParentGuid;
livemark.index = newIndex;
}
});
},
onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid) {
if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
return;
- this._promiseLivemarksMap().then(livemarksMap => {
+ this._withLivemarksMap(livemarksMap => {
if (livemarksMap.has(guid)) {
let livemark = livemarksMap.get(guid);
livemark.terminate();
livemarksMap.delete(guid);
}
});
},
// nsINavHistoryObserver
onPageChanged() {},
onTitleChanged() {},
onDeleteVisits() {},
onClearHistory() {
- this._promiseLivemarksMap().then(livemarksMap => {
+ this._withLivemarksMap(livemarksMap => {
for (let livemark of livemarksMap.values()) {
livemark.updateURIVisitedStatus(null, false);
}
});
},
onDeleteURI(aURI) {
- this._promiseLivemarksMap().then(livemarksMap => {
+ this._withLivemarksMap(livemarksMap => {
for (let livemark of livemarksMap.values()) {
livemark.updateURIVisitedStatus(aURI, false);
}
});
},
onVisit(aURI) {
- this._promiseLivemarksMap().then(livemarksMap => {
+ this._withLivemarksMap(livemarksMap => {
for (let livemark of livemarksMap.values()) {
livemark.updateURIVisitedStatus(aURI, true);
}
});
},
// nsISupports
--- a/toolkit/components/places/tests/unit/test_async_transactions.js
+++ b/toolkit/components/places/tests/unit/test_async_transactions.js
@@ -329,17 +329,18 @@ async function ensureEqualBookmarksTrees
for (let property of Object.keys(aOriginal)) {
if (property == "children") {
Assert.equal(aOriginal.children.length, aNew.children.length);
for (let i = 0; i < aOriginal.children.length; i++) {
await ensureEqualBookmarksTrees(aOriginal.children[i],
aNew.children[i],
false,
- true);
+ true,
+ aIgnoreAllDates);
}
} else if (property == "guid") {
// guid shouldn't be copied if the item was not restored.
Assert.notEqual(aOriginal.guid, aNew.guid);
} else if (property == "dateAdded") {
// dateAdded shouldn't be copied if the item was not restored.
Assert.ok(is_time_ordered(aOriginal.dateAdded, aNew.dateAdded));
} else if (property == "lastModified") {
@@ -982,26 +983,26 @@ add_task(async function test_add_and_rem
let removeTxn = PT.Remove(guid);
await removeTxn.transact();
await ensureNonExistent(guid);
async function undo() {
ensureUndoState([[removeTxn], [createLivemarkTxn]], 0);
await PT.undo();
ensureUndoState([[removeTxn], [createLivemarkTxn]], 1);
- await ensureBookmarksTreeRestoredCorrectly(originalInfo);
+ await ensureBookmarksTreeRestoredCorrectlyExceptDates(originalInfo);
await PT.undo();
ensureUndoState([[removeTxn], [createLivemarkTxn]], 2);
await ensureNonExistent(guid);
}
async function redo() {
ensureUndoState([[removeTxn], [createLivemarkTxn]], 2);
await PT.redo();
ensureUndoState([[removeTxn], [createLivemarkTxn]], 1);
- await ensureBookmarksTreeRestoredCorrectly(originalInfo);
+ await ensureBookmarksTreeRestoredCorrectlyExceptDates(originalInfo);
await PT.redo();
ensureUndoState([[removeTxn], [createLivemarkTxn]], 0);
await ensureNonExistent(guid);
}
await undo();
await redo();
await undo();