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:
Bob Silverberg 2016-05-13 11:09:06 -04:00
Родитель b610707ecb
Коммит 77cec8a034
5 изменённых файлов: 581 добавлений и 80 удалений

Просмотреть файл

@ -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]