зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1265836 - Part 3: Implement insert and insertMany in History.jsm. r=mak
MozReview-Commit-ID: GmXVDPuULtq --HG-- extra : transplant_source : %02%AB%9DZ%8F%E8ER%AE%2A%7E%A3L%89%DC%11g7%DF%C4
This commit is contained in:
Родитель
b610707ecb
Коммит
77cec8a034
|
@ -176,11 +176,11 @@ var Bookmarks = Object.freeze({
|
|||
let observers = PlacesUtils.bookmarks.getObservers();
|
||||
// We need the itemId to notify, though once the switch to guids is
|
||||
// complete we may stop using it.
|
||||
let uri = item.hasOwnProperty("url") ? toURI(item.url) : null;
|
||||
let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
|
||||
let itemId = yield PlacesUtils.promiseItemId(item.guid);
|
||||
notify(observers, "onItemAdded", [ itemId, parent._id, item.index,
|
||||
item.type, uri, item.title || null,
|
||||
toPRTime(item.dateAdded), item.guid,
|
||||
PlacesUtils.toPRTime(item.dateAdded), item.guid,
|
||||
item.parentGuid ]);
|
||||
|
||||
// If it's a tag, notify OnItemChanged to all bookmarks for this URL.
|
||||
|
@ -188,7 +188,7 @@ var Bookmarks = Object.freeze({
|
|||
if (isTagging) {
|
||||
for (let entry of (yield fetchBookmarksByURL(item))) {
|
||||
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
|
||||
toPRTime(entry.lastModified),
|
||||
PlacesUtils.toPRTime(entry.lastModified),
|
||||
entry.type, entry._parentId,
|
||||
entry.guid, entry.parentGuid,
|
||||
"" ]);
|
||||
|
@ -323,8 +323,8 @@ var Bookmarks = Object.freeze({
|
|||
item.lastModified != updatedItem.lastModified) {
|
||||
notify(observers, "onItemChanged", [ updatedItem._id, "lastModified",
|
||||
false,
|
||||
`${toPRTime(updatedItem.lastModified)}`,
|
||||
toPRTime(updatedItem.lastModified),
|
||||
`${PlacesUtils.toPRTime(updatedItem.lastModified)}`,
|
||||
PlacesUtils.toPRTime(updatedItem.lastModified),
|
||||
updatedItem.type,
|
||||
updatedItem._parentId,
|
||||
updatedItem.guid,
|
||||
|
@ -333,7 +333,7 @@ var Bookmarks = Object.freeze({
|
|||
if (updateInfo.hasOwnProperty("title")) {
|
||||
notify(observers, "onItemChanged", [ updatedItem._id, "title",
|
||||
false, updatedItem.title,
|
||||
toPRTime(updatedItem.lastModified),
|
||||
PlacesUtils.toPRTime(updatedItem.lastModified),
|
||||
updatedItem.type,
|
||||
updatedItem._parentId,
|
||||
updatedItem.guid,
|
||||
|
@ -342,7 +342,7 @@ var Bookmarks = Object.freeze({
|
|||
if (updateInfo.hasOwnProperty("url")) {
|
||||
notify(observers, "onItemChanged", [ updatedItem._id, "uri",
|
||||
false, updatedItem.url.href,
|
||||
toPRTime(updatedItem.lastModified),
|
||||
PlacesUtils.toPRTime(updatedItem.lastModified),
|
||||
updatedItem.type,
|
||||
updatedItem._parentId,
|
||||
updatedItem.guid,
|
||||
|
@ -408,7 +408,7 @@ var Bookmarks = Object.freeze({
|
|||
|
||||
// Notify onItemRemoved to listeners.
|
||||
let observers = PlacesUtils.bookmarks.getObservers();
|
||||
let uri = item.hasOwnProperty("url") ? toURI(item.url) : null;
|
||||
let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
|
||||
notify(observers, "onItemRemoved", [ item._id, item._parentId, item.index,
|
||||
item.type, uri, item.guid,
|
||||
item.parentGuid ]);
|
||||
|
@ -417,7 +417,7 @@ var Bookmarks = Object.freeze({
|
|||
if (isUntagging) {
|
||||
for (let entry of (yield fetchBookmarksByURL(item))) {
|
||||
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
|
||||
toPRTime(entry.lastModified),
|
||||
PlacesUtils.toPRTime(entry.lastModified),
|
||||
entry.type, entry._parentId,
|
||||
entry.guid, entry.parentGuid,
|
||||
"" ]);
|
||||
|
@ -442,7 +442,7 @@ var Bookmarks = Object.freeze({
|
|||
db => db.executeTransaction(function* () {
|
||||
const folderGuids = [this.toolbarGuid, this.menuGuid, this.unfiledGuid];
|
||||
yield removeFoldersContents(db, folderGuids);
|
||||
const time = toPRTime(new Date());
|
||||
const time = PlacesUtils.toPRTime(new Date());
|
||||
for (let folderGuid of folderGuids) {
|
||||
yield db.executeCached(
|
||||
`UPDATE moz_bookmarks SET lastModified = :time
|
||||
|
@ -769,7 +769,7 @@ function updateBookmark(info, item, newParent) {
|
|||
|
||||
let tuples = new Map();
|
||||
if (info.hasOwnProperty("lastModified"))
|
||||
tuples.set("lastModified", { value: toPRTime(info.lastModified) });
|
||||
tuples.set("lastModified", { value: PlacesUtils.toPRTime(info.lastModified) });
|
||||
if (info.hasOwnProperty("title"))
|
||||
tuples.set("title", { value: info.title });
|
||||
|
||||
|
@ -886,8 +886,8 @@ function insertBookmark(item, parent) {
|
|||
:index, :title, :date_added, :last_modified, :guid)
|
||||
`, { url: item.hasOwnProperty("url") ? item.url.href : "nonexistent",
|
||||
type: item.type, parent: parent._id, index: item.index,
|
||||
title: item.title, date_added: toPRTime(item.dateAdded),
|
||||
last_modified: toPRTime(item.lastModified), guid: item.guid });
|
||||
title: item.title, date_added: PlacesUtils.toPRTime(item.dateAdded),
|
||||
last_modified: PlacesUtils.toPRTime(item.lastModified), guid: item.guid });
|
||||
|
||||
yield setAncestorsLastModified(db, item.parentGuid, item.dateAdded);
|
||||
});
|
||||
|
@ -1239,39 +1239,6 @@ function removeSameValueProperties(dest, src) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an URL object to an nsIURI.
|
||||
*
|
||||
* @param url
|
||||
* the URL object to convert.
|
||||
* @return nsIURI for the given URL.
|
||||
*/
|
||||
function toURI(url) {
|
||||
return NetUtil.newURI(url.href);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function toDate(time) {
|
||||
return new Date(parseInt(time / 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of mozIStorageRow objects to an array of bookmark objects.
|
||||
*
|
||||
|
@ -1286,7 +1253,7 @@ function rowsToItemsArray(rows) {
|
|||
item[prop] = row.getResultByName(prop);
|
||||
}
|
||||
for (let prop of ["dateAdded", "lastModified"]) {
|
||||
item[prop] = toDate(row.getResultByName(prop));
|
||||
item[prop] = PlacesUtils.toDate(row.getResultByName(prop));
|
||||
}
|
||||
for (let prop of ["title", "parentGuid", "url" ]) {
|
||||
let val = row.getResultByName(prop);
|
||||
|
@ -1332,7 +1299,7 @@ function simpleValidateFunc(boolValidateFn) {
|
|||
*/
|
||||
const VALIDATORS = Object.freeze({
|
||||
guid: simpleValidateFunc(v => typeof(v) == "string" &&
|
||||
/^[a-zA-Z0-9\-_]{12}$/.test(v)),
|
||||
PlacesUtils.isValidGuid(v)),
|
||||
parentGuid: simpleValidateFunc(v => typeof(v) == "string" &&
|
||||
/^[a-zA-Z0-9\-_]{12}$/.test(v)),
|
||||
index: simpleValidateFunc(v => Number.isInteger(v) &&
|
||||
|
@ -1515,7 +1482,7 @@ var setAncestorsLastModified = Task.async(function* (db, folderGuid, time) {
|
|||
UPDATE moz_bookmarks SET lastModified = :time
|
||||
WHERE id IN ancestors
|
||||
`, { guid: folderGuid, type: Bookmarks.TYPE_FOLDER,
|
||||
time: toPRTime(time) });
|
||||
time: PlacesUtils.toPRTime(time) });
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -1580,7 +1547,7 @@ Task.async(function* (db, folderGuids) {
|
|||
// Notify listeners in reverse order to serve children before parents.
|
||||
let observers = PlacesUtils.bookmarks.getObservers();
|
||||
for (let item of itemsRemoved.reverse()) {
|
||||
let uri = item.hasOwnProperty("url") ? toURI(item.url) : null;
|
||||
let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
|
||||
notify(observers, "onItemRemoved", [ item._id, item._parentId,
|
||||
item.index, item.type, uri,
|
||||
item.guid, item.parentGuid ]);
|
||||
|
@ -1589,7 +1556,7 @@ Task.async(function* (db, folderGuids) {
|
|||
if (isUntagging) {
|
||||
for (let entry of (yield fetchBookmarksByURL(item))) {
|
||||
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
|
||||
toPRTime(entry.lastModified),
|
||||
PlacesUtils.toPRTime(entry.lastModified),
|
||||
entry.type, entry._parentId,
|
||||
entry.guid, entry.parentGuid,
|
||||
"" ]);
|
||||
|
|
|
@ -132,38 +132,28 @@ this.History = Object.freeze({
|
|||
},
|
||||
|
||||
/**
|
||||
* Adds a set of visits for one or more page.
|
||||
* Adds a number of visits for a single page.
|
||||
*
|
||||
* Any change may be observed through nsINavHistoryObserver
|
||||
*
|
||||
* @note This function recomputes the frecency of the page automatically,
|
||||
* regardless of the value of property `frecency` passed as argument.
|
||||
* @note If there is no entry for the page, the entry is created.
|
||||
*
|
||||
* @param infos: (PageInfo)
|
||||
* @param pageInfo: (PageInfo)
|
||||
* Information on a page. This `PageInfo` MUST contain
|
||||
* - either a property `guid` or a property `url`, as specified
|
||||
* by the definition of `PageInfo`;
|
||||
* - a property `url`, as specified by the definition of `PageInfo`.
|
||||
* - a property `visits`, as specified by the definition of
|
||||
* `PageInfo`, which MUST contain at least one visit.
|
||||
* If a property `title` is provided, the title of the page
|
||||
* is updated.
|
||||
* If the `visitDate` of a visit is not provided, it defaults
|
||||
* If the `date` of a visit is not provided, it defaults
|
||||
* to now.
|
||||
* or (Array<PageInfo>)
|
||||
* An array of the above, to batch requests.
|
||||
* @param onResult: (function(PageInfo), [optional])
|
||||
* A callback invoked for each page, with the updated
|
||||
* information on that page. Note that this `PageInfo`
|
||||
* does NOT contain the visit data (i.e. `visits` is
|
||||
* `undefined`).
|
||||
* If the `transition` of a visit is not provided, it defaults to
|
||||
* TRANSITION_LINK.
|
||||
*
|
||||
* @return (Promise)
|
||||
* A promise resolved once the operation is complete, including
|
||||
* all calls to `onResult`.
|
||||
* @resolves (bool)
|
||||
* `true` if at least one page entry was created, `false` otherwise
|
||||
* (i.e. if page entries were updated but not created).
|
||||
* A promise resolved once the operation is complete.
|
||||
* @resolves (PageInfo)
|
||||
* A PageInfo object populated with data after the insert is complete.
|
||||
* @rejects (Error)
|
||||
* Rejects if the insert was unsuccessful.
|
||||
*
|
||||
* @throws (Error)
|
||||
* If the `url` specified was for a protocol that should not be
|
||||
|
@ -171,22 +161,97 @@ this.History = Object.freeze({
|
|||
* "moz-anno:", "view-source:", "resource:", "data:", "wyciwyg:",
|
||||
* "javascript:", "blob:").
|
||||
* @throws (Error)
|
||||
* If `infos` has an unexpected type.
|
||||
* If `pageInfo` has an unexpected type.
|
||||
* @throws (Error)
|
||||
* If a `PageInfo` has neither `guid` nor `url`.
|
||||
* If `pageInfo` does not have a `url`.
|
||||
* @throws (Error)
|
||||
* If a `guid` property provided is not a valid GUID.
|
||||
* If `pageInfo` does not have a `visits` property or if the
|
||||
* value of `visits` is ill-typed or is an empty array.
|
||||
* @throws (Error)
|
||||
* If an element of `visits` has an invalid `date`.
|
||||
* @throws (Error)
|
||||
* If an element of `visits` has an invalid `transition`.
|
||||
*/
|
||||
insert: function (pageInfo) {
|
||||
if (typeof pageInfo != "object" || !pageInfo) {
|
||||
throw new TypeError("pageInfo must be an object");
|
||||
}
|
||||
|
||||
let info = validatePageInfo(pageInfo);
|
||||
|
||||
return PlacesUtils.withConnectionWrapper("History.jsm: insert",
|
||||
db => insert(db, info));
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a number of visits for a number of pages.
|
||||
*
|
||||
* Any change may be observed through nsINavHistoryObserver
|
||||
*
|
||||
* @param pageInfos: (Array<PageInfo>)
|
||||
* Information on a page. This `PageInfo` MUST contain
|
||||
* - a property `url`, as specified by the definition of `PageInfo`.
|
||||
* - a property `visits`, as specified by the definition of
|
||||
* `PageInfo`, which MUST contain at least one visit.
|
||||
* If a property `title` is provided, the title of the page
|
||||
* is updated.
|
||||
* If the `date` of a visit is not provided, it defaults
|
||||
* to now.
|
||||
* If the `transition` of a visit is not provided, it defaults to
|
||||
* TRANSITION_LINK.
|
||||
* @param onResult: (function(PageInfo))
|
||||
* A callback invoked for each page inserted.
|
||||
* @param onError: (function(PageInfo))
|
||||
* A callback invoked for each page which generated an error
|
||||
* when an insert was attempted.
|
||||
*
|
||||
* @return (Promise)
|
||||
* A promise resolved once the operation is complete.
|
||||
* @resolves (null)
|
||||
* @rejects (Error)
|
||||
* Rejects if all of the inserts were unsuccessful.
|
||||
*
|
||||
* @throws (Error)
|
||||
* If the `url` specified was for a protocol that should not be
|
||||
* stored (e.g. "chrome:", "mailbox:", "about:", "imap:", "news:",
|
||||
* "moz-anno:", "view-source:", "resource:", "data:", "wyciwyg:",
|
||||
* "javascript:", "blob:").
|
||||
* @throws (Error)
|
||||
* If `pageInfos` has an unexpected type.
|
||||
* @throws (Error)
|
||||
* If a `pageInfo` does not have a `url`.
|
||||
* @throws (Error)
|
||||
* If a `PageInfo` does not have a `visits` property or if the
|
||||
* value of `visits` is ill-typed or is an empty array.
|
||||
* @throws (Error)
|
||||
* If an element of `visits` has an invalid `date`.
|
||||
* @throws (Error)
|
||||
* If an element of `visits` is missing `transition` or if
|
||||
* the value of `transition` is invalid.
|
||||
* If an element of `visits` has an invalid `transition`.
|
||||
*/
|
||||
update: function (infos, onResult) {
|
||||
throw new Error("Method not implemented");
|
||||
insertMany: function (pageInfos, onResult, onError) {
|
||||
let infos = [];
|
||||
|
||||
if (!Array.isArray(pageInfos)) {
|
||||
throw new TypeError("pageInfos must be an array");
|
||||
}
|
||||
if (!pageInfos.length) {
|
||||
throw new TypeError("pageInfos may not be an empty array");
|
||||
}
|
||||
|
||||
if (onResult && typeof onResult != "function") {
|
||||
throw new TypeError(`onResult: ${onResult} is not a valid function`);
|
||||
}
|
||||
if (onError && typeof onError != "function") {
|
||||
throw new TypeError(`onError: ${onError} is not a valid function`);
|
||||
}
|
||||
|
||||
for (let pageInfo of pageInfos) {
|
||||
let info = validatePageInfo(pageInfo);
|
||||
infos.push(info);
|
||||
}
|
||||
|
||||
return PlacesUtils.withConnectionWrapper("History.jsm: insertMany",
|
||||
db => insertMany(db, infos, onResult, onError));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -390,6 +455,103 @@ this.History = Object.freeze({
|
|||
TRANSITION_FRAMED_LINK: Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK,
|
||||
});
|
||||
|
||||
/**
|
||||
* Validate an input PageInfo object, returning a valid PageInfo object.
|
||||
*
|
||||
* @param pageInfo: (PageInfo)
|
||||
* @return (PageInfo)
|
||||
*/
|
||||
function validatePageInfo(pageInfo) {
|
||||
let info = {
|
||||
visits: [],
|
||||
};
|
||||
|
||||
if (!pageInfo.url) {
|
||||
throw new TypeError("PageInfo object must have a url property");
|
||||
}
|
||||
|
||||
info.url = normalizeToURLOrGUID(pageInfo.url);
|
||||
|
||||
if (typeof pageInfo.title === "string" && pageInfo.title.length) {
|
||||
info.title = pageInfo.title;
|
||||
} else if (pageInfo.title != null && pageInfo.title != undefined) {
|
||||
throw new TypeError(`title property of PageInfo object: ${pageInfo.title} must be a string if provided`);
|
||||
}
|
||||
|
||||
if (!pageInfo.visits || !Array.isArray(pageInfo.visits) || !pageInfo.visits.length) {
|
||||
throw new TypeError("PageInfo object must have an array of visits");
|
||||
}
|
||||
for (let inVisit of pageInfo.visits) {
|
||||
let visit = {
|
||||
date: new Date(),
|
||||
transition: inVisit.transition || History.TRANSITION_LINK,
|
||||
};
|
||||
|
||||
if (!isValidTransitionType(visit.transition)) {
|
||||
throw new TypeError(`transition: ${visit.transition} is not a valid transition type`);
|
||||
}
|
||||
|
||||
if (inVisit.date) {
|
||||
ensureDate(inVisit.date);
|
||||
if (inVisit.date > Date.now()) {
|
||||
throw new TypeError(`date: ${inVisit.date} cannot be a future date`);
|
||||
}
|
||||
visit.date = inVisit.date;
|
||||
}
|
||||
|
||||
if (inVisit.referrer) {
|
||||
visit.referrer = normalizeToURLOrGUID(inVisit.referrer);
|
||||
}
|
||||
info.visits.push(visit);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a PageInfo object into the format expected by updatePlaces.
|
||||
*
|
||||
* Note: this assumes that the PageInfo object has already been validated
|
||||
* via validatePageInfo.
|
||||
*
|
||||
* @param pageInfo: (PageInfo)
|
||||
* @return (info)
|
||||
*/
|
||||
function convertForUpdatePlaces(pageInfo) {
|
||||
let info = {
|
||||
uri: PlacesUtils.toURI(pageInfo.url),
|
||||
title: pageInfo.title,
|
||||
visits: [],
|
||||
};
|
||||
|
||||
for (let inVisit of pageInfo.visits) {
|
||||
let visit = {
|
||||
visitDate: PlacesUtils.toPRTime(inVisit.date),
|
||||
transitionType: inVisit.transition,
|
||||
referrerURI: (inVisit.referrer) ? PlacesUtils.toURI(inVisit.referrer) : undefined,
|
||||
};
|
||||
info.visits.push(visit);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a value a valid transition type?
|
||||
*
|
||||
* @param transitionType: (String)
|
||||
* @return (Boolean)
|
||||
*/
|
||||
function isValidTransitionType(transitionType) {
|
||||
return [
|
||||
History.TRANSITION_LINK,
|
||||
History.TRANSITION_TYPED,
|
||||
History.TRANSITION_BOOKMARK,
|
||||
History.TRANSITION_EMBED,
|
||||
History.TRANSITION_REDIRECT_PERMANENT,
|
||||
History.TRANSITION_REDIRECT_TEMPORARY,
|
||||
History.TRANSITION_DOWNLOAD,
|
||||
History.TRANSITION_FRAMED_LINK
|
||||
].includes(transitionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a key to either a string (if it is a valid GUID) or an
|
||||
|
@ -402,7 +564,7 @@ this.History = Object.freeze({
|
|||
function normalizeToURLOrGUID(key) {
|
||||
if (typeof key === "string") {
|
||||
// A string may be a URL or a guid
|
||||
if (/^[a-zA-Z0-9\-_]{12}$/.test(key)) {
|
||||
if (PlacesUtils.isValidGuid(key)) {
|
||||
return key;
|
||||
}
|
||||
return new URL(key);
|
||||
|
@ -767,3 +929,87 @@ var remove = Task.async(function*(db, {guids, urls}, onResult = null) {
|
|||
|
||||
return hasPagesToRemove;
|
||||
});
|
||||
|
||||
/**
|
||||
* Merges an updateInfo object, as returned by asyncHistory.updatePlaces
|
||||
* into a PageInfo object as defined in this file.
|
||||
*
|
||||
* @param updateInfo: (Object)
|
||||
* An object that represents a page that is generated by
|
||||
* asyncHistory.updatePlaces.
|
||||
* @param pageInfo: (PageInfo)
|
||||
* An PageInfo object into which to merge the data from updateInfo.
|
||||
* Defaults to an empty object so that this method can be used
|
||||
* to simply convert an updateInfo object into a PageInfo object.
|
||||
*
|
||||
* @return (PageInfo)
|
||||
* A PageInfo object populated with data from updateInfo.
|
||||
*/
|
||||
function mergeUpdateInfoIntoPageInfo(updateInfo, pageInfo={}) {
|
||||
pageInfo.guid = updateInfo.guid;
|
||||
if (!pageInfo.url) {
|
||||
pageInfo.url = new URL(updateInfo.uri.spec);
|
||||
pageInfo.title = updateInfo.title;
|
||||
pageInfo.visits = updateInfo.visits.map(visit => {
|
||||
return {
|
||||
date: PlacesUtils.toDate(visit.visitDate),
|
||||
transition: visit.transitionType,
|
||||
referrer: (visit.referrerURI) ? new URL(visit.referrerURI.spec) : null
|
||||
}
|
||||
});
|
||||
}
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
// Inner implementation of History.insert.
|
||||
var insert = Task.async(function*(db, pageInfo) {
|
||||
let info = convertForUpdatePlaces(pageInfo);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
PlacesUtils.asyncHistory.updatePlaces(info, {
|
||||
handleError: error => {
|
||||
reject(error);
|
||||
},
|
||||
handleResult: result => {
|
||||
pageInfo = mergeUpdateInfoIntoPageInfo(result, pageInfo);
|
||||
},
|
||||
handleCompletion: () => {
|
||||
resolve(pageInfo);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Inner implementation of History.insertMany.
|
||||
var insertMany = Task.async(function*(db, pageInfos, onResult, onError) {
|
||||
let infos = [];
|
||||
let onResultData = [];
|
||||
let onErrorData = [];
|
||||
|
||||
for (let pageInfo of pageInfos) {
|
||||
let info = convertForUpdatePlaces(pageInfo);
|
||||
infos.push(info);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
PlacesUtils.asyncHistory.updatePlaces(infos, {
|
||||
handleError: (resultCode, result) => {
|
||||
let pageInfo = mergeUpdateInfoIntoPageInfo(result);
|
||||
onErrorData.push(pageInfo);
|
||||
},
|
||||
handleResult: result => {
|
||||
let pageInfo = mergeUpdateInfoIntoPageInfo(result);
|
||||
onResultData.push(pageInfo);
|
||||
},
|
||||
handleCompletion: () => {
|
||||
notifyOnResult(onResultData, onResult);
|
||||
notifyOnResult(onErrorData, onError);
|
||||
if (onResultData.length) {
|
||||
resolve();
|
||||
} else {
|
||||
reject({message: "No items were added to history."})
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -260,6 +260,29 @@ this.PlacesUtils = {
|
|||
return NetUtil.newURI(aSpec);
|
||||
},
|
||||
|
||||
/**
|
||||
* Is a string a valid GUID?
|
||||
*
|
||||
* @param guid: (String)
|
||||
* @return (Boolean)
|
||||
*/
|
||||
isValidGuid(guid) {
|
||||
return (/^[a-zA-Z0-9\-_]{12}$/.test(guid));
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a string or n URL object to an nsIURI.
|
||||
*
|
||||
* @param url (URL) or (String)
|
||||
* the URL to convert.
|
||||
* @return nsIURI for the given URL.
|
||||
*/
|
||||
toURI(url) {
|
||||
url = (url instanceof URL) ? url.href : url;
|
||||
|
||||
return NetUtil.newURI(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert a Date object to a PRTime (microseconds).
|
||||
*
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
|
||||
// Tests for `History.insert` and `History.insertMany`, as implemented in History.jsm
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(function* test_insert_error_cases() {
|
||||
const TEST_URL = "http://mozilla.com";
|
||||
|
||||
let validPageInfo = {
|
||||
url: TEST_URL,
|
||||
visits: [
|
||||
{transition: TRANSITION_LINK}
|
||||
]
|
||||
};
|
||||
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert(),
|
||||
/TypeError: pageInfo must be an object/,
|
||||
"passing a null into History.insert should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert(1),
|
||||
/TypeError: pageInfo must be an object/,
|
||||
"passing a non object into History.insert should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert({}),
|
||||
/TypeError: PageInfo object must have a url property/,
|
||||
"passing an object without a url to History.insert should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert({url: 123}),
|
||||
/TypeError: Invalid url or guid: 123/,
|
||||
"passing an object with an invalid url to History.insert should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert({url: TEST_URL}),
|
||||
/TypeError: PageInfo object must have an array of visits/,
|
||||
"passing an object without a visits property to History.insert should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert({url: TEST_URL, visits: 1}),
|
||||
/TypeError: PageInfo object must have an array of visits/,
|
||||
"passing an object with a non-array visits property to History.insert should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert({url: TEST_URL, visits: []}),
|
||||
/TypeError: PageInfo object must have an array of visits/,
|
||||
"passing an object with an empty array as the visits property to History.insert should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert({
|
||||
url: TEST_URL,
|
||||
visits: [
|
||||
{
|
||||
transition: TRANSITION_LINK,
|
||||
date: "a"
|
||||
}
|
||||
]}),
|
||||
/TypeError: Expected a Date, got a/,
|
||||
"passing a visit object with an invalid date to History.insert should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert({
|
||||
url: TEST_URL,
|
||||
visits: [
|
||||
{
|
||||
transition: TRANSITION_LINK
|
||||
},
|
||||
{
|
||||
transition: TRANSITION_LINK,
|
||||
date: "a"
|
||||
}
|
||||
]}),
|
||||
/TypeError: Expected a Date, got a/,
|
||||
"passing a second visit object with an invalid date to History.insert should throw a TypeError"
|
||||
);
|
||||
let futureDate = new Date();
|
||||
futureDate.setDate(futureDate.getDate() + 1);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert({
|
||||
url: TEST_URL,
|
||||
visits: [
|
||||
{
|
||||
transition: TRANSITION_LINK,
|
||||
date: futureDate,
|
||||
}
|
||||
]}),
|
||||
`TypeError: date: ${futureDate} is not a valid date`,
|
||||
"passing a visit object with a future date to History.insert should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insert({
|
||||
url: TEST_URL,
|
||||
visits: [
|
||||
{transition: "a"}
|
||||
]}),
|
||||
/TypeError: transition: a is not a valid transition type/,
|
||||
"passing a visit object with an invalid transition to History.insert should throw a TypeError"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function* test_history_insert() {
|
||||
const TEST_URL = "http://mozilla.com/";
|
||||
|
||||
let inserter = Task.async(function*(name, filter, referrer, date, transition) {
|
||||
do_print(name);
|
||||
do_print(`filter: ${filter}, referrer: ${referrer}, date: ${date}, transition: ${transition}`);
|
||||
|
||||
let uri = NetUtil.newURI(TEST_URL + Math.random());
|
||||
let title = "Visit " + Math.random();
|
||||
|
||||
let pageInfo = {
|
||||
title,
|
||||
visits: [
|
||||
{transition: transition, referrer: referrer, date: date,}
|
||||
]
|
||||
};
|
||||
|
||||
pageInfo.url = yield filter(uri);
|
||||
|
||||
let result = yield PlacesUtils.history.insert(pageInfo);
|
||||
|
||||
Assert.ok(PlacesUtils.isValidGuid(result.guid), "guid for pageInfo object is valid");
|
||||
Assert.equal(uri.spec, result.url.href, "url is correct for pageInfo object");
|
||||
Assert.equal(title, result.title, "title is correct for pageInfo object");
|
||||
Assert.equal(TRANSITION_LINK, result.visits[0].transition, "transition is correct for pageInfo object");
|
||||
if (referrer) {
|
||||
Assert.equal(referrer, result.visits[0].referrer.href, "url of referrer for visit is correct");
|
||||
} else {
|
||||
Assert.equal(null, result.visits[0].referrer, "url of referrer for visit is correct");
|
||||
}
|
||||
if (date) {
|
||||
Assert.equal(Number(date),
|
||||
Number(result.visits[0].date),
|
||||
"date of visit is correct");
|
||||
}
|
||||
|
||||
Assert.ok(yield PlacesTestUtils.isPageInDB(uri), "Page was added");
|
||||
Assert.ok(yield PlacesTestUtils.visitsInDB(uri), "Visit was added");
|
||||
});
|
||||
|
||||
try {
|
||||
for (let referrer of [TEST_URL, null]) {
|
||||
for (let date of [new Date(), null]) {
|
||||
for (let transition of [TRANSITION_LINK, null]) {
|
||||
yield inserter("Testing History.insert() with an nsIURI", x => x, referrer, date, transition);
|
||||
yield inserter("Testing History.insert() with a string url", x => x.spec, referrer, date, transition);
|
||||
yield inserter("Testing History.insert() with a URL object", x => new URL(x.spec), referrer, date, transition);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_insert_multiple_error_cases() {
|
||||
let validPageInfo = {
|
||||
url: "http://mozilla.com",
|
||||
visits: [
|
||||
{transition: TRANSITION_LINK}
|
||||
]
|
||||
};
|
||||
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insertMany(),
|
||||
/TypeError: pageInfos must be an array/,
|
||||
"passing a null into History.insertMany should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insertMany([]),
|
||||
/TypeError: pageInfos may not be an empty array/,
|
||||
"passing an empty array into History.insertMany should throw a TypeError"
|
||||
);
|
||||
Assert.throws(
|
||||
() => PlacesUtils.history.insertMany([validPageInfo, {}]),
|
||||
/TypeError: PageInfo object must have a url property/,
|
||||
"passing a second invalid PageInfo object to History.insertMany should throw a TypeError"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function* test_history_insertMany() {
|
||||
const BAD_URLS = ["about:config", "chrome://browser/content/browser.xul"];
|
||||
const GOOD_URLS = [1, 2, 3].map(x => {return `http://mozilla.com/${x}`;});
|
||||
|
||||
let makePageInfos = Task.async(function*(urls, filter = x => x) {
|
||||
let pageInfos = [];
|
||||
for (let url of urls) {
|
||||
let uri = NetUtil.newURI(url);
|
||||
|
||||
let pageInfo = {
|
||||
title: `Visit to ${url}`,
|
||||
visits: [
|
||||
{transition: TRANSITION_LINK}
|
||||
]
|
||||
};
|
||||
|
||||
pageInfo.url = yield filter(uri);
|
||||
pageInfos.push(pageInfo);
|
||||
}
|
||||
return pageInfos;
|
||||
});
|
||||
|
||||
let inserter = Task.async(function*(name, filter, useCallbacks) {
|
||||
do_print(name);
|
||||
do_print(`filter: ${filter}`);
|
||||
do_print(`useCallbacks: ${useCallbacks}`);
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
|
||||
let result;
|
||||
let allUrls = GOOD_URLS.concat(BAD_URLS);
|
||||
let pageInfos = yield makePageInfos(allUrls, filter);
|
||||
|
||||
if (useCallbacks) {
|
||||
let onResultUrls = [];
|
||||
let onErrorUrls = [];
|
||||
result = yield PlacesUtils.history.insertMany(pageInfos, pageInfo => {
|
||||
let url = pageInfo.url.href;
|
||||
Assert.ok(GOOD_URLS.includes(url), "onResult callback called for correct url");
|
||||
onResultUrls.push(url);
|
||||
Assert.equal(`Visit to ${url}`, pageInfo.title, "onResult callback provides the correct title");
|
||||
Assert.ok(PlacesUtils.isValidGuid(pageInfo.guid), "onResult callback provides a valid guid");
|
||||
}, pageInfo => {
|
||||
let url = pageInfo.url.href;
|
||||
Assert.ok(BAD_URLS.includes(url), "onError callback called for correct uri");
|
||||
onErrorUrls.push(url);
|
||||
Assert.equal(undefined, pageInfo.title, "onError callback provides the correct title");
|
||||
Assert.equal(undefined, pageInfo.guid, "onError callback provides the expected guid");
|
||||
});
|
||||
Assert.equal(GOOD_URLS.sort().toString(), onResultUrls.sort().toString(), "onResult callback was called for each good url");
|
||||
Assert.equal(BAD_URLS.sort().toString(), onErrorUrls.sort().toString(), "onError callback was called for each bad url");
|
||||
} else {
|
||||
result = yield PlacesUtils.history.insertMany(pageInfos);
|
||||
}
|
||||
|
||||
Assert.equal(undefined, result, "insertMany returned undefined");
|
||||
|
||||
for (let url of allUrls) {
|
||||
let expected = GOOD_URLS.includes(url);
|
||||
Assert.equal(expected, yield PlacesTestUtils.isPageInDB(url), `isPageInDB for ${url} is ${expected}`);
|
||||
Assert.equal(expected, yield PlacesTestUtils.visitsInDB(url), `visitsInDB for ${url} is ${expected}`);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
for (let useCallbacks of [false, true]) {
|
||||
yield inserter("Testing History.insertMany() with an nsIURI", x => x, useCallbacks);
|
||||
yield inserter("Testing History.insertMany() with a string url", x => x.spec, useCallbacks);
|
||||
yield inserter("Testing History.insertMany() with a URL object", x => new URL(x.spec), useCallbacks);
|
||||
}
|
||||
// Test rejection when no items added
|
||||
let pageInfos = yield makePageInfos(BAD_URLS);
|
||||
PlacesUtils.history.insertMany(pageInfos).then(() => {
|
||||
Assert.ok(false, "History.insertMany rejected promise with all bad URLs");
|
||||
}, error => {
|
||||
Assert.equal("No items were added to history.", error.message, "History.insertMany rejected promise with all bad URLs");
|
||||
});
|
||||
} finally {
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
}
|
||||
});
|
|
@ -2,6 +2,7 @@
|
|||
head = head_history.js
|
||||
tail =
|
||||
|
||||
[test_insert.js]
|
||||
[test_remove.js]
|
||||
[test_removeVisits.js]
|
||||
[test_removeVisitsByFilter.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче