import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,9 @@
"use strict";
module.exports = {
"extends": [
"../../../../testing/mochitest/mochitest.eslintrc.js",
"../../../../testing/mochitest/chrome.eslintrc.js",
"../../../../testing/xpcshell/xpcshell.eslintrc.js"
]
};

View file

@ -0,0 +1,163 @@
"use strict";
this.EXPORTED_SYMBOLS = [
"PlacesTestUtils",
];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.importGlobalProperties(["URL"]);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
this.PlacesTestUtils = Object.freeze({
/**
* Asynchronously adds visits to a page.
*
* @param aPlaceInfo
* Can be an nsIURI, in such a case a single LINK visit will be added.
* Otherwise can be an object describing the visit to add, or an array
* of these objects:
* { uri: nsIURI of the page,
* [optional] transition: one of the TRANSITION_* from nsINavHistoryService,
* [optional] title: title of the page,
* [optional] visitDate: visit date, either in microseconds from the epoch or as a date object
* [optional] referrer: nsIURI of the referrer for this visit
* }
*
* @return {Promise}
* @resolves When all visits have been added successfully.
* @rejects JavaScript exception.
*/
addVisits: Task.async(function* (placeInfo) {
let places = [];
let infos = [];
if (placeInfo instanceof Ci.nsIURI ||
placeInfo instanceof URL ||
typeof placeInfo == "string") {
places.push({ uri: placeInfo });
}
else if (Array.isArray(placeInfo)) {
places = places.concat(placeInfo);
} else if (typeof placeInfo == "object" && placeInfo.uri) {
places.push(placeInfo)
} else {
throw new Error("Unsupported type passed to addVisits");
}
// Create a PageInfo for each entry.
for (let place of places) {
let info = {url: place.uri};
info.title = (typeof place.title === "string") ? place.title : "test visit for " + info.url.spec ;
if (typeof place.referrer == "string") {
place.referrer = NetUtil.newURI(place.referrer);
} else if (place.referrer && place.referrer instanceof URL) {
place.referrer = NetUtil.newURI(place.referrer.href);
}
let visitDate = place.visitDate;
if (visitDate) {
if (!(visitDate instanceof Date)) {
visitDate = PlacesUtils.toDate(visitDate);
}
} else {
visitDate = new Date();
}
info.visits = [{
transition: place.transition,
date: visitDate,
referrer: place.referrer
}];
infos.push(info);
}
return PlacesUtils.history.insertMany(infos);
}),
/**
* Clear all history.
*
* @return {Promise}
* @resolves When history was cleared successfully.
* @rejects JavaScript exception.
*/
clearHistory() {
let expirationFinished = new Promise(resolve => {
Services.obs.addObserver(function observe(subj, topic, data) {
Services.obs.removeObserver(observe, topic);
resolve();
}, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
});
return Promise.all([expirationFinished, PlacesUtils.history.clear()]);
},
/**
* Waits for all pending async statements on the default connection.
*
* @return {Promise}
* @resolves When all pending async statements finished.
* @rejects Never.
*
* @note The result is achieved by asynchronously executing a query requiring
* a write lock. Since all statements on the same connection are
* serialized, the end of this write operation means that all writes are
* complete. Note that WAL makes so that writers don't block readers, but
* this is a problem only across different connections.
*/
promiseAsyncUpdates() {
return PlacesUtils.withConnectionWrapper("promiseAsyncUpdates", Task.async(function* (db) {
try {
yield db.executeCached("BEGIN EXCLUSIVE");
yield db.executeCached("COMMIT");
} catch (ex) {
// If we fail to start a transaction, it's because there is already one.
// In such a case we should not try to commit the existing transaction.
}
}));
},
/**
* Asynchronously checks if an address is found in the database.
* @param aURI
* nsIURI or address to look for.
*
* @return {Promise}
* @resolves Returns true if the page is found.
* @rejects JavaScript exception.
*/
isPageInDB: Task.async(function* (aURI) {
let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.executeCached(
"SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url",
{ url });
return rows.length > 0;
}),
/**
* Asynchronously checks how many visits exist for a specified page.
* @param aURI
* nsIURI or address to look for.
*
* @return {Promise}
* @resolves Returns the number of visits found.
* @rejects JavaScript exception.
*/
visitsInDB: Task.async(function* (aURI) {
let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.executeCached(
`SELECT count(*) FROM moz_historyvisits v
JOIN moz_places h ON h.id = v.place_id
WHERE url_hash = hash(:url) AND url = :url`,
{ url });
return rows[0].getResultByIndex(0);
})
});

View file

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"../../../../../testing/xpcshell/xpcshell.eslintrc.js"
]
};

View file

@ -0,0 +1,20 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
var Ci = Components.interfaces;
var Cc = Components.classes;
var Cr = Components.results;
var Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm");
// Import common head.
{
let commonFile = do_get_file("../head_common.js", false);
let uri = Services.io.newFileURI(commonFile);
Services.scriptloader.loadSubScript(uri.spec, this);
}
// Put any other stuff relative to this test folder below.

View file

@ -0,0 +1,103 @@
function run_test() {
run_next_test();
}
/* Bug 1016953 - When a previous bookmark backup exists with the same hash
regardless of date, an automatic backup should attempt to either rename it to
today's date if the backup was for an old date or leave it alone if it was for
the same date. However if the file ext was json it will accidentally rename it
to jsonlz4 while keeping the json contents
*/
add_task(function* test_same_date_same_hash() {
// If old file has been created on the same date and has the same hash
// the file should be left alone
let backupFolder = yield PlacesBackups.getBackupFolder();
// Save to profile dir to obtain hash and nodeCount to append to filename
let tempPath = OS.Path.join(OS.Constants.Path.profileDir,
"bug10169583_bookmarks.json");
let {count, hash} = yield BookmarkJSONUtils.exportToFile(tempPath);
// Save JSON file in backup folder with hash appended
let dateObj = new Date();
let filename = "bookmarks-" + PlacesBackups.toISODateString(dateObj) + "_" +
count + "_" + hash + ".json";
let backupFile = OS.Path.join(backupFolder, filename);
yield OS.File.move(tempPath, backupFile);
// Force a compressed backup which fallbacks to rename
yield PlacesBackups.create();
let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
// check to ensure not renamed to jsonlz4
Assert.equal(mostRecentBackupFile, backupFile);
// inspect contents and check if valid json
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let result = yield OS.File.read(mostRecentBackupFile);
let jsonString = converter.convertFromByteArray(result, result.length);
do_print("Check is valid JSON");
JSON.parse(jsonString);
// Cleanup
yield OS.File.remove(backupFile);
yield OS.File.remove(tempPath);
PlacesBackups._backupFiles = null; // To force re-cache of backupFiles
});
add_task(function* test_same_date_diff_hash() {
// If the old file has been created on the same date, but has a different hash
// the existing file should be overwritten with the newer compressed version
let backupFolder = yield PlacesBackups.getBackupFolder();
let tempPath = OS.Path.join(OS.Constants.Path.profileDir,
"bug10169583_bookmarks.json");
let {count} = yield BookmarkJSONUtils.exportToFile(tempPath);
let dateObj = new Date();
let filename = "bookmarks-" + PlacesBackups.toISODateString(dateObj) + "_" +
count + "_" + "differentHash==" + ".json";
let backupFile = OS.Path.join(backupFolder, filename);
yield OS.File.move(tempPath, backupFile);
yield PlacesBackups.create(); // Force compressed backup
mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
// Decode lz4 compressed file to json and check if json is valid
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let result = yield OS.File.read(mostRecentBackupFile, { compression: "lz4" });
let jsonString = converter.convertFromByteArray(result, result.length);
do_print("Check is valid JSON");
JSON.parse(jsonString);
// Cleanup
yield OS.File.remove(mostRecentBackupFile);
yield OS.File.remove(tempPath);
PlacesBackups._backupFiles = null; // To force re-cache of backupFiles
});
add_task(function* test_diff_date_same_hash() {
// If the old file has been created on an older day but has the same hash
// it should be renamed with today's date without altering the contents.
let backupFolder = yield PlacesBackups.getBackupFolder();
let tempPath = OS.Path.join(OS.Constants.Path.profileDir,
"bug10169583_bookmarks.json");
let {count, hash} = yield BookmarkJSONUtils.exportToFile(tempPath);
let oldDate = new Date(2014, 1, 1);
let curDate = new Date();
let oldFilename = "bookmarks-" + PlacesBackups.toISODateString(oldDate) + "_" +
count + "_" + hash + ".json";
let newFilename = "bookmarks-" + PlacesBackups.toISODateString(curDate) + "_" +
count + "_" + hash + ".json";
let backupFile = OS.Path.join(backupFolder, oldFilename);
let newBackupFile = OS.Path.join(backupFolder, newFilename);
yield OS.File.move(tempPath, backupFile);
// Ensure file has been renamed correctly
yield PlacesBackups.create();
let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
Assert.equal(mostRecentBackupFile, newBackupFile);
// Cleanup
yield OS.File.remove(mostRecentBackupFile);
yield OS.File.remove(tempPath);
});

View file

@ -0,0 +1,112 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/* Bug 1017502 - Add a foreign_count column to moz_places
This tests, tests the triggers that adjust the foreign_count when a bookmark is
added or removed and also the maintenance task to fix wrong counts.
*/
const T_URI = NetUtil.newURI("https://www.mozilla.org/firefox/nightly/firstrun/");
function* getForeignCountForURL(conn, url) {
yield PlacesTestUtils.promiseAsyncUpdates();
url = url instanceof Ci.nsIURI ? url.spec : url;
let rows = yield conn.executeCached(
`SELECT foreign_count FROM moz_places WHERE url_hash = hash(:t_url)
AND url = :t_url`, { t_url: url });
return rows[0].getResultByName("foreign_count");
}
function run_test() {
run_next_test();
}
add_task(function* add_remove_change_bookmark_test() {
let conn = yield PlacesUtils.promiseDBConnection();
// Simulate a visit to the url
yield PlacesTestUtils.addVisits(T_URI);
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 0);
// Add 1st bookmark which should increment foreign_count by 1
let id1 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
T_URI, PlacesUtils.bookmarks.DEFAULT_INDEX, "First Run");
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 1);
// Add 2nd bookmark
let id2 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId,
T_URI, PlacesUtils.bookmarks.DEFAULT_INDEX, "First Run");
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 2);
// Remove 2nd bookmark which should decrement foreign_count by 1
PlacesUtils.bookmarks.removeItem(id2);
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 1);
// Change first bookmark's URI
const URI2 = NetUtil.newURI("http://www.mozilla.org");
PlacesUtils.bookmarks.changeBookmarkURI(id1, URI2);
// Check foreign count for original URI
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 0);
// Check foreign count for new URI
Assert.equal((yield getForeignCountForURL(conn, URI2)), 1);
// Cleanup - Remove changed bookmark
let id = PlacesUtils.bookmarks.getBookmarkIdsForURI(URI2);
PlacesUtils.bookmarks.removeItem(id);
Assert.equal((yield getForeignCountForURL(conn, URI2)), 0);
});
add_task(function* maintenance_foreign_count_test() {
let conn = yield PlacesUtils.promiseDBConnection();
// Simulate a visit to the url
yield PlacesTestUtils.addVisits(T_URI);
// Adjust the foreign_count for the added entry to an incorrect value
let deferred = Promise.defer();
let stmt = DBConn().createAsyncStatement(
`UPDATE moz_places SET foreign_count = 10 WHERE url_hash = hash(:t_url)
AND url = :t_url `);
stmt.params.t_url = T_URI.spec;
stmt.executeAsync({
handleCompletion: function() {
deferred.resolve();
}
});
stmt.finalize();
yield deferred.promise;
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 10);
// Run maintenance
Components.utils.import("resource://gre/modules/PlacesDBUtils.jsm");
let promiseMaintenanceFinished =
promiseTopicObserved("places-maintenance-finished");
PlacesDBUtils.maintenanceOnIdle();
yield promiseMaintenanceFinished;
// Check if the foreign_count has been adjusted to the correct value
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 0);
});
add_task(function* add_remove_tags_test() {
let conn = yield PlacesUtils.promiseDBConnection();
yield PlacesTestUtils.addVisits(T_URI);
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 0);
// Check foreign count incremented by 1 for a single tag
PlacesUtils.tagging.tagURI(T_URI, ["test tag"]);
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 1);
// Check foreign count is incremented by 2 for two tags
PlacesUtils.tagging.tagURI(T_URI, ["one", "two"]);
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 3);
// Check foreign count is set to 0 when all tags are removed
PlacesUtils.tagging.untagURI(T_URI, ["test tag", "one", "two"]);
Assert.equal((yield getForeignCountForURL(conn, T_URI)), 0);
});

View file

@ -0,0 +1,76 @@
var now = Date.now() * 1000;
// Test that importing bookmark data where a bookmark has a tag longer than 100
// chars imports everything except the tags for that bookmark.
add_task(function* () {
let aData = {
guid: "root________",
index: 0,
id: 1,
type: "text/x-moz-place-container",
dateAdded: now,
lastModified: now,
root: "placesRoot",
children: [{
guid: "unfiled_____",
index: 0,
id: 2,
type: "text/x-moz-place-container",
dateAdded: now,
lastModified: now,
root: "unfiledBookmarksFolder",
children: [
{
guid: "___guid1____",
index: 0,
id: 3,
charset: "UTF-8",
tags: "tag0",
type: "text/x-moz-place",
dateAdded: now,
lastModified: now,
uri: "http://test0.com/"
},
{
guid: "___guid2____",
index: 1,
id: 4,
charset: "UTF-8",
tags: "tag1," + "a" + "0123456789".repeat(10), // 101 chars
type: "text/x-moz-place",
dateAdded: now,
lastModified: now,
uri: "http://test1.com/"
},
{
guid: "___guid3____",
index: 2,
id: 5,
charset: "UTF-8",
tags: "tag2",
type: "text/x-moz-place",
dateAdded: now,
lastModified: now,
uri: "http://test2.com/"
}
]
}]
};
let contentType = "application/json";
let uri = "data:" + contentType + "," + JSON.stringify(aData);
yield BookmarkJSONUtils.importFromURL(uri, false);
let [bookmarks] = yield PlacesBackups.getBookmarksTree();
let unsortedBookmarks = bookmarks.children[2].children;
Assert.equal(unsortedBookmarks.length, 3);
for (let i = 0; i < unsortedBookmarks.length; ++i) {
let bookmark = unsortedBookmarks[i];
Assert.equal(bookmark.charset, "UTF-8");
Assert.equal(bookmark.dateAdded, now);
Assert.equal(bookmark.lastModified, now);
Assert.equal(bookmark.uri, "http://test" + i + ".com/");
Assert.equal(bookmark.tags, "tag" + i);
}
});

View file

@ -0,0 +1,98 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* test querying for bookmarks in multiple folders.
*/
add_task(function* search_bookmark_in_folder() {
let testFolder1 = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "bug 384228 test folder 1"
});
Assert.equal(testFolder1.index, 0);
let testFolder2 = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "bug 384228 test folder 2"
});
Assert.equal(testFolder2.index, 1);
let testFolder3 = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "bug 384228 test folder 3"
});
Assert.equal(testFolder3.index, 2);
let b1 = yield PlacesUtils.bookmarks.insert({
parentGuid: testFolder1.guid,
url: "http://foo.tld/",
title: "title b1 (folder 1)"
});
Assert.equal(b1.index, 0);
let b2 = yield PlacesUtils.bookmarks.insert({
parentGuid: testFolder1.guid,
url: "http://foo.tld/",
title: "title b2 (folder 1)"
});
Assert.equal(b2.index, 1);
let b3 = yield PlacesUtils.bookmarks.insert({
parentGuid: testFolder2.guid,
url: "http://foo.tld/",
title: "title b3 (folder 2)"
});
Assert.equal(b3.index, 0);
let b4 = yield PlacesUtils.bookmarks.insert({
parentGuid: testFolder3.guid,
url: "http://foo.tld/",
title: "title b4 (folder 3)"
});
Assert.equal(b4.index, 0);
// also test recursive search
let testFolder1_1 = yield PlacesUtils.bookmarks.insert({
parentGuid: testFolder1.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "bug 384228 test folder 1.1"
});
Assert.equal(testFolder1_1.index, 2);
let b5 = yield PlacesUtils.bookmarks.insert({
parentGuid: testFolder1_1.guid,
url: "http://foo.tld/",
title: "title b5 (folder 1.1)"
});
Assert.equal(b5.index, 0);
// query folder 1, folder 2 and get 4 bookmarks
let folderIds = [];
folderIds.push(yield PlacesUtils.promiseItemId(testFolder1.guid));
folderIds.push(yield PlacesUtils.promiseItemId(testFolder2.guid));
let hs = PlacesUtils.history;
let options = hs.getNewQueryOptions();
let query = hs.getNewQuery();
query.searchTerms = "title";
options.queryType = options.QUERY_TYPE_BOOKMARKS;
query.setFolders(folderIds, folderIds.length);
let rootNode = hs.executeQuery(query, options).root;
rootNode.containerOpen = true;
// should not match item from folder 3
Assert.equal(rootNode.childCount, 4);
Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
Assert.equal(rootNode.getChild(3).bookmarkGuid, b5.guid);
rootNode.containerOpen = false;
});

View file

@ -0,0 +1,182 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
add_task(function* search_bookmark_by_lastModified_dateDated() {
// test search on folder with various sorts and max results
// see bug #385829 for more details
let folder = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "bug 385829 test"
});
let now = new Date();
// ensure some unique values for date added and last modified
// for date added: b1 < b2 < b3 < b4
// for last modified: b1 > b2 > b3 > b4
let b1 = yield PlacesUtils.bookmarks.insert({
parentGuid: folder.guid,
url: "http://a1.com/",
title: "1 title",
dateAdded: new Date(now.getTime() + 1000)
});
let b2 = yield PlacesUtils.bookmarks.insert({
parentGuid: folder.guid,
url: "http://a2.com/",
title: "2 title",
dateAdded: new Date(now.getTime() + 2000)
});
let b3 = yield PlacesUtils.bookmarks.insert({
parentGuid: folder.guid,
url: "http://a3.com/",
title: "3 title",
dateAdded: new Date(now.getTime() + 3000)
});
let b4 = yield PlacesUtils.bookmarks.insert({
parentGuid: folder.guid,
url: "http://a4.com/",
title: "4 title",
dateAdded: new Date(now.getTime() + 4000)
});
// make sure lastModified is larger than dateAdded
let modifiedTime = new Date(now.getTime() + 5000);
yield PlacesUtils.bookmarks.update({
guid: b1.guid,
lastModified: new Date(modifiedTime.getTime() + 4000)
});
yield PlacesUtils.bookmarks.update({
guid: b2.guid,
lastModified: new Date(modifiedTime.getTime() + 3000)
});
yield PlacesUtils.bookmarks.update({
guid: b3.guid,
lastModified: new Date(modifiedTime.getTime() + 2000)
});
yield PlacesUtils.bookmarks.update({
guid: b4.guid,
lastModified: new Date(modifiedTime.getTime() + 1000)
});
let hs = PlacesUtils.history;
let options = hs.getNewQueryOptions();
let query = hs.getNewQuery();
options.queryType = options.QUERY_TYPE_BOOKMARKS;
options.maxResults = 3;
let folderIds = [];
folderIds.push(yield PlacesUtils.promiseItemId(folder.guid));
query.setFolders(folderIds, 1);
let result = hs.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
// test SORT_BY_DATEADDED_ASCENDING (live update)
result.sortingMode = options.SORT_BY_DATEADDED_ASCENDING;
Assert.equal(rootNode.childCount, 3);
Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
Assert.ok(rootNode.getChild(0).dateAdded <
rootNode.getChild(1).dateAdded);
Assert.ok(rootNode.getChild(1).dateAdded <
rootNode.getChild(2).dateAdded);
// test SORT_BY_DATEADDED_DESCENDING (live update)
result.sortingMode = options.SORT_BY_DATEADDED_DESCENDING;
Assert.equal(rootNode.childCount, 3);
Assert.equal(rootNode.getChild(0).bookmarkGuid, b3.guid);
Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
Assert.equal(rootNode.getChild(2).bookmarkGuid, b1.guid);
Assert.ok(rootNode.getChild(0).dateAdded >
rootNode.getChild(1).dateAdded);
Assert.ok(rootNode.getChild(1).dateAdded >
rootNode.getChild(2).dateAdded);
// test SORT_BY_LASTMODIFIED_ASCENDING (live update)
result.sortingMode = options.SORT_BY_LASTMODIFIED_ASCENDING;
Assert.equal(rootNode.childCount, 3);
Assert.equal(rootNode.getChild(0).bookmarkGuid, b3.guid);
Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
Assert.equal(rootNode.getChild(2).bookmarkGuid, b1.guid);
Assert.ok(rootNode.getChild(0).lastModified <
rootNode.getChild(1).lastModified);
Assert.ok(rootNode.getChild(1).lastModified <
rootNode.getChild(2).lastModified);
// test SORT_BY_LASTMODIFIED_DESCENDING (live update)
result.sortingMode = options.SORT_BY_LASTMODIFIED_DESCENDING;
Assert.equal(rootNode.childCount, 3);
Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
Assert.ok(rootNode.getChild(0).lastModified >
rootNode.getChild(1).lastModified);
Assert.ok(rootNode.getChild(1).lastModified >
rootNode.getChild(2).lastModified);
rootNode.containerOpen = false;
// test SORT_BY_DATEADDED_ASCENDING
options.sortingMode = options.SORT_BY_DATEADDED_ASCENDING;
result = hs.executeQuery(query, options);
rootNode = result.root;
rootNode.containerOpen = true;
Assert.equal(rootNode.childCount, 3);
Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
Assert.ok(rootNode.getChild(0).dateAdded <
rootNode.getChild(1).dateAdded);
Assert.ok(rootNode.getChild(1).dateAdded <
rootNode.getChild(2).dateAdded);
rootNode.containerOpen = false;
// test SORT_BY_DATEADDED_DESCENDING
options.sortingMode = options.SORT_BY_DATEADDED_DESCENDING;
result = hs.executeQuery(query, options);
rootNode = result.root;
rootNode.containerOpen = true;
Assert.equal(rootNode.childCount, 3);
Assert.equal(rootNode.getChild(0).bookmarkGuid, b4.guid);
Assert.equal(rootNode.getChild(1).bookmarkGuid, b3.guid);
Assert.equal(rootNode.getChild(2).bookmarkGuid, b2.guid);
Assert.ok(rootNode.getChild(0).dateAdded >
rootNode.getChild(1).dateAdded);
Assert.ok(rootNode.getChild(1).dateAdded >
rootNode.getChild(2).dateAdded);
rootNode.containerOpen = false;
// test SORT_BY_LASTMODIFIED_ASCENDING
options.sortingMode = options.SORT_BY_LASTMODIFIED_ASCENDING;
result = hs.executeQuery(query, options);
rootNode = result.root;
rootNode.containerOpen = true;
Assert.equal(rootNode.childCount, 3);
Assert.equal(rootNode.getChild(0).bookmarkGuid, b4.guid);
Assert.equal(rootNode.getChild(1).bookmarkGuid, b3.guid);
Assert.equal(rootNode.getChild(2).bookmarkGuid, b2.guid);
Assert.ok(rootNode.getChild(0).lastModified <
rootNode.getChild(1).lastModified);
Assert.ok(rootNode.getChild(1).lastModified <
rootNode.getChild(2).lastModified);
rootNode.containerOpen = false;
// test SORT_BY_LASTMODIFIED_DESCENDING
options.sortingMode = options.SORT_BY_LASTMODIFIED_DESCENDING;
result = hs.executeQuery(query, options);
rootNode = result.root;
rootNode.containerOpen = true;
Assert.equal(rootNode.childCount, 3);
Assert.equal(rootNode.getChild(0).bookmarkGuid, b1.guid);
Assert.equal(rootNode.getChild(1).bookmarkGuid, b2.guid);
Assert.equal(rootNode.getChild(2).bookmarkGuid, b3.guid);
Assert.ok(rootNode.getChild(0).lastModified >
rootNode.getChild(1).lastModified);
Assert.ok(rootNode.getChild(1).lastModified >
rootNode.getChild(2).lastModified);
rootNode.containerOpen = false;
});

View file

@ -0,0 +1,52 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
// Get bookmark service
try {
var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
} catch (ex) {
do_throw("Could not get nav-bookmarks-service\n");
}
var gTestRoot;
var gURI;
var gItemId1;
var gItemId2;
// main
function run_test() {
gURI = uri("http://foo.tld.com/");
gTestRoot = bmsvc.createFolder(bmsvc.placesRoot, "test folder",
bmsvc.DEFAULT_INDEX);
// test getBookmarkIdsForURI
// getBookmarkIdsForURI sorts by the most recently added/modified (descending)
//
// we cannot rely on dateAdded growing when doing so in a simple iteration,
// see PR_Now() documentation
do_test_pending();
gItemId1 = bmsvc.insertBookmark(gTestRoot, gURI, bmsvc.DEFAULT_INDEX, "");
do_timeout(100, phase2);
}
function phase2() {
gItemId2 = bmsvc.insertBookmark(gTestRoot, gURI, bmsvc.DEFAULT_INDEX, "");
var b = bmsvc.getBookmarkIdsForURI(gURI);
do_check_eq(b[0], gItemId2);
do_check_eq(b[1], gItemId1);
do_timeout(100, phase3);
}
function phase3() {
// trigger last modified change
bmsvc.setItemTitle(gItemId1, "");
var b = bmsvc.getBookmarkIdsForURI(gURI);
do_check_eq(b[0], gItemId1);
do_check_eq(b[1], gItemId2);
do_test_finished();
}

View file

@ -0,0 +1,102 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
var observer = {
__proto__: NavBookmarkObserver.prototype,
onItemAdded: function (id, folder, index) {
this._itemAddedId = id;
this._itemAddedParent = folder;
this._itemAddedIndex = index;
},
onItemChanged: function (id, property, isAnnotationProperty, value) {
this._itemChangedId = id;
this._itemChangedProperty = property;
this._itemChanged_isAnnotationProperty = isAnnotationProperty;
this._itemChangedValue = value;
}
};
PlacesUtils.bookmarks.addObserver(observer, false);
do_register_cleanup(function () {
PlacesUtils.bookmarks.removeObserver(observer);
});
function run_test() {
// We set times in the past to workaround a timing bug due to virtual
// machines and the skew between PR_Now() and Date.now(), see bug 427142 and
// bug 858377 for details.
const PAST_PRTIME = (Date.now() - 86400000) * 1000;
// Insert a new bookmark.
let testFolder = PlacesUtils.bookmarks.createFolder(
PlacesUtils.placesRootId, "test Folder",
PlacesUtils.bookmarks.DEFAULT_INDEX);
let bookmarkId = PlacesUtils.bookmarks.insertBookmark(
testFolder, uri("http://google.com/"),
PlacesUtils.bookmarks.DEFAULT_INDEX, "");
// Sanity check.
do_check_true(observer.itemChangedProperty === undefined);
// Set dateAdded in the past and verify the bookmarks cache.
PlacesUtils.bookmarks.setItemDateAdded(bookmarkId, PAST_PRTIME);
do_check_eq(observer._itemChangedProperty, "dateAdded");
do_check_eq(observer._itemChangedValue, PAST_PRTIME);
let dateAdded = PlacesUtils.bookmarks.getItemDateAdded(bookmarkId);
do_check_eq(dateAdded, PAST_PRTIME);
// After just inserting, modified should be the same as dateAdded.
do_check_eq(PlacesUtils.bookmarks.getItemLastModified(bookmarkId), dateAdded);
// Set lastModified in the past and verify the bookmarks cache.
PlacesUtils.bookmarks.setItemLastModified(bookmarkId, PAST_PRTIME);
do_check_eq(observer._itemChangedProperty, "lastModified");
do_check_eq(observer._itemChangedValue, PAST_PRTIME);
do_check_eq(PlacesUtils.bookmarks.getItemLastModified(bookmarkId),
PAST_PRTIME);
// Set bookmark title
PlacesUtils.bookmarks.setItemTitle(bookmarkId, "Google");
// Test notifications.
do_check_eq(observer._itemChangedId, bookmarkId);
do_check_eq(observer._itemChangedProperty, "title");
do_check_eq(observer._itemChangedValue, "Google");
// Check lastModified has been updated.
is_time_ordered(PAST_PRTIME,
PlacesUtils.bookmarks.getItemLastModified(bookmarkId));
// Check that node properties are updated.
let root = PlacesUtils.getFolderContents(testFolder).root;
do_check_eq(root.childCount, 1);
let childNode = root.getChild(0);
// confirm current dates match node properties
do_check_eq(PlacesUtils.bookmarks.getItemDateAdded(bookmarkId),
childNode.dateAdded);
do_check_eq(PlacesUtils.bookmarks.getItemLastModified(bookmarkId),
childNode.lastModified);
// Test live update of lastModified when setting title.
PlacesUtils.bookmarks.setItemLastModified(bookmarkId, PAST_PRTIME);
PlacesUtils.bookmarks.setItemTitle(bookmarkId, "Google");
// Check lastModified has been updated.
is_time_ordered(PAST_PRTIME, childNode.lastModified);
// Test that node value matches db value.
do_check_eq(PlacesUtils.bookmarks.getItemLastModified(bookmarkId),
childNode.lastModified);
// Test live update of the exposed date apis.
PlacesUtils.bookmarks.setItemDateAdded(bookmarkId, PAST_PRTIME);
do_check_eq(childNode.dateAdded, PAST_PRTIME);
PlacesUtils.bookmarks.setItemLastModified(bookmarkId, PAST_PRTIME);
do_check_eq(childNode.lastModified, PAST_PRTIME);
root.containerOpen = false;
}

View file

@ -0,0 +1,87 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
// Get bookmark service
try {
var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].getService(Ci.nsINavBookmarksService);
} catch (ex) {
do_throw("Could not get nav-bookmarks-service\n");
}
// Get history service
try {
var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].getService(Ci.nsINavHistoryService);
} catch (ex) {
do_throw("Could not get history service\n");
}
// Get tagging service
try {
var tagssvc = Cc["@mozilla.org/browser/tagging-service;1"].
getService(Ci.nsITaggingService);
} catch (ex) {
do_throw("Could not get tagging service\n");
}
// get bookmarks root id
var root = bmsvc.bookmarksMenuFolder;
// main
function run_test() {
// test searching for tagged bookmarks
// test folder
var folder = bmsvc.createFolder(root, "bug 395101 test", bmsvc.DEFAULT_INDEX);
// create a bookmark
var testURI = uri("http://a1.com");
var b1 = bmsvc.insertBookmark(folder, testURI,
bmsvc.DEFAULT_INDEX, "1 title");
// tag the bookmarked URI
tagssvc.tagURI(testURI, ["elephant", "walrus", "giraffe", "turkey", "hiPPo", "BABOON", "alf"]);
// search for the bookmark, using a tag
var query = histsvc.getNewQuery();
query.searchTerms = "elephant";
var options = histsvc.getNewQueryOptions();
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
query.setFolders([folder], 1);
var result = histsvc.executeQuery(query, options);
var rootNode = result.root;
rootNode.containerOpen = true;
do_check_eq(rootNode.childCount, 1);
do_check_eq(rootNode.getChild(0).itemId, b1);
rootNode.containerOpen = false;
// partial matches are okay
query.searchTerms = "wal";
result = histsvc.executeQuery(query, options);
rootNode = result.root;
rootNode.containerOpen = true;
do_check_eq(rootNode.childCount, 1);
rootNode.containerOpen = false;
// case insensitive search term
query.searchTerms = "WALRUS";
result = histsvc.executeQuery(query, options);
rootNode = result.root;
rootNode.containerOpen = true;
do_check_eq(rootNode.childCount, 1);
do_check_eq(rootNode.getChild(0).itemId, b1);
rootNode.containerOpen = false;
// case insensitive tag
query.searchTerms = "baboon";
result = histsvc.executeQuery(query, options);
rootNode = result.root;
rootNode.containerOpen = true;
do_check_eq(rootNode.childCount, 1);
do_check_eq(rootNode.getChild(0).itemId, b1);
rootNode.containerOpen = false;
}

View file

@ -0,0 +1,69 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
function check_queries_results(aQueries, aOptions, aExpectedItemIds) {
var result = hs.executeQueries(aQueries, aQueries.length, aOptions);
var root = result.root;
root.containerOpen = true;
// Dump found nodes.
for (let i = 0; i < root.childCount; i++) {
dump("nodes[" + i + "]: " + root.getChild(0).title + "\n");
}
do_check_eq(root.childCount, aExpectedItemIds.length);
for (let i = 0; i < root.childCount; i++) {
do_check_eq(root.getChild(i).itemId, aExpectedItemIds[i]);
}
root.containerOpen = false;
}
// main
function run_test() {
var id1 = bs.insertBookmark(bs.bookmarksMenuFolder, uri("http://foo.tld"),
bs.DEFAULT_INDEX, "123 0");
var id2 = bs.insertBookmark(bs.bookmarksMenuFolder, uri("http://foo.tld"),
bs.DEFAULT_INDEX, "456");
var id3 = bs.insertBookmark(bs.bookmarksMenuFolder, uri("http://foo.tld"),
bs.DEFAULT_INDEX, "123 456");
var id4 = bs.insertBookmark(bs.bookmarksMenuFolder, uri("http://foo.tld"),
bs.DEFAULT_INDEX, "789 456");
/**
* All of the query objects are ORed together. Within a query, all the terms
* are ANDed together. See nsINavHistory.idl.
*/
var queries = [];
queries.push(hs.getNewQuery());
queries.push(hs.getNewQuery());
var options = hs.getNewQueryOptions();
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
// Test 1
dump("Test searching for 123 OR 789\n");
queries[0].searchTerms = "123";
queries[1].searchTerms = "789";
check_queries_results(queries, options, [id1, id3, id4]);
// Test 2
dump("Test searching for 123 OR 456\n");
queries[0].searchTerms = "123";
queries[1].searchTerms = "456";
check_queries_results(queries, options, [id1, id2, id3, id4]);
// Test 3
dump("Test searching for 00 OR 789\n");
queries[0].searchTerms = "00";
queries[1].searchTerms = "789";
check_queries_results(queries, options, [id4]);
}

View file

@ -0,0 +1,221 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
var tests = [];
/*
Backup/restore tests example:
var myTest = {
populate: function () { ... add bookmarks ... },
validate: function () { ... query for your bookmarks ... }
}
this.push(myTest);
*/
/*
test summary:
- create folders with content
- create a query bookmark for those folders
- backs up bookmarks
- restores bookmarks
- confirms that the query has the new ids for the same folders
scenarios:
- 1 folder (folder shortcut)
- n folders (single query)
- n folders (multiple queries)
*/
const DEFAULT_INDEX = PlacesUtils.bookmarks.DEFAULT_INDEX;
var test = {
_testRootId: null,
_testRootTitle: "test root",
_folderIds: [],
_bookmarkURIs: [],
_count: 3,
populate: function populate() {
// folder to hold this test
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.toolbarFolderId);
this._testRootId =
PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId,
this._testRootTitle, DEFAULT_INDEX);
// create test folders each with a bookmark
for (var i = 0; i < this._count; i++) {
var folderId =
PlacesUtils.bookmarks.createFolder(this._testRootId, "folder" + i, DEFAULT_INDEX);
this._folderIds.push(folderId)
var bookmarkURI = uri("http://" + i);
PlacesUtils.bookmarks.insertBookmark(folderId, bookmarkURI,
DEFAULT_INDEX, "bookmark" + i);
this._bookmarkURIs.push(bookmarkURI);
}
// create a query URI with 1 folder (ie: folder shortcut)
this._queryURI1 = uri("place:folder=" + this._folderIds[0] + "&queryType=1");
this._queryTitle1 = "query1";
PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI1,
DEFAULT_INDEX, this._queryTitle1);
// create a query URI with _count folders
this._queryURI2 = uri("place:folder=" + this._folderIds.join("&folder=") + "&queryType=1");
this._queryTitle2 = "query2";
PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI2,
DEFAULT_INDEX, this._queryTitle2);
// create a query URI with _count queries (each with a folder)
// first get a query object for each folder
var queries = this._folderIds.map(function(aFolderId) {
var query = PlacesUtils.history.getNewQuery();
query.setFolders([aFolderId], 1);
return query;
});
var options = PlacesUtils.history.getNewQueryOptions();
options.queryType = options.QUERY_TYPE_BOOKMARKS;
this._queryURI3 =
uri(PlacesUtils.history.queriesToQueryString(queries, queries.length, options));
this._queryTitle3 = "query3";
PlacesUtils.bookmarks.insertBookmark(this._testRootId, this._queryURI3,
DEFAULT_INDEX, this._queryTitle3);
},
clean: function () {},
validate: function validate() {
// Throw a wrench in the works by inserting some new bookmarks,
// ensuring folder ids won't be the same, when restoring.
for (let i = 0; i < 10; i++) {
PlacesUtils.bookmarks.
insertBookmark(PlacesUtils.bookmarksMenuFolderId, uri("http://aaaa"+i), DEFAULT_INDEX, "");
}
var toolbar =
PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId,
false, true).root;
do_check_true(toolbar.childCount, 1);
var folderNode = toolbar.getChild(0);
do_check_eq(folderNode.type, folderNode.RESULT_TYPE_FOLDER);
do_check_eq(folderNode.title, this._testRootTitle);
folderNode.QueryInterface(Ci.nsINavHistoryQueryResultNode);
folderNode.containerOpen = true;
// |_count| folders + the query node
do_check_eq(folderNode.childCount, this._count+3);
for (let i = 0; i < this._count; i++) {
var subFolder = folderNode.getChild(i);
do_check_eq(subFolder.title, "folder"+i);
subFolder.QueryInterface(Ci.nsINavHistoryContainerResultNode);
subFolder.containerOpen = true;
do_check_eq(subFolder.childCount, 1);
var child = subFolder.getChild(0);
do_check_eq(child.title, "bookmark"+i);
do_check_true(uri(child.uri).equals(uri("http://" + i)))
}
// validate folder shortcut
this.validateQueryNode1(folderNode.getChild(this._count));
// validate folders query
this.validateQueryNode2(folderNode.getChild(this._count + 1));
// validate multiple queries query
this.validateQueryNode3(folderNode.getChild(this._count + 2));
// clean up
folderNode.containerOpen = false;
toolbar.containerOpen = false;
},
validateQueryNode1: function validateQueryNode1(aNode) {
do_check_eq(aNode.title, this._queryTitle1);
do_check_true(PlacesUtils.nodeIsFolder(aNode));
aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
aNode.containerOpen = true;
do_check_eq(aNode.childCount, 1);
var child = aNode.getChild(0);
do_check_true(uri(child.uri).equals(uri("http://0")))
do_check_eq(child.title, "bookmark0")
aNode.containerOpen = false;
},
validateQueryNode2: function validateQueryNode2(aNode) {
do_check_eq(aNode.title, this._queryTitle2);
do_check_true(PlacesUtils.nodeIsQuery(aNode));
aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
aNode.containerOpen = true;
do_check_eq(aNode.childCount, this._count);
for (var i = 0; i < aNode.childCount; i++) {
var child = aNode.getChild(i);
do_check_true(uri(child.uri).equals(uri("http://" + i)))
do_check_eq(child.title, "bookmark" + i)
}
aNode.containerOpen = false;
},
validateQueryNode3: function validateQueryNode3(aNode) {
do_check_eq(aNode.title, this._queryTitle3);
do_check_true(PlacesUtils.nodeIsQuery(aNode));
aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
aNode.containerOpen = true;
do_check_eq(aNode.childCount, this._count);
for (var i = 0; i < aNode.childCount; i++) {
var child = aNode.getChild(i);
do_check_true(uri(child.uri).equals(uri("http://" + i)))
do_check_eq(child.title, "bookmark" + i)
}
aNode.containerOpen = false;
}
}
tests.push(test);
function run_test() {
run_next_test();
}
add_task(function* () {
// make json file
let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
// populate db
tests.forEach(function(aTest) {
aTest.populate();
// sanity
aTest.validate();
});
// export json to file
yield BookmarkJSONUtils.exportToFile(jsonFile);
// clean
tests.forEach(function(aTest) {
aTest.clean();
});
// restore json file
yield BookmarkJSONUtils.importFromFile(jsonFile, true);
// validate
tests.forEach(function(aTest) {
aTest.validate();
});
// clean up
yield OS.File.remove(jsonFile);
});

View file

@ -0,0 +1,141 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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 EXCLUDE_FROM_BACKUP_ANNO = "places/excludeFromBackup";
// Menu, Toolbar, Unsorted, Tags, Mobile
const PLACES_ROOTS_COUNT = 5;
var tests = [];
/*
Backup/restore tests example:
var myTest = {
populate: function () { ... add bookmarks ... },
validate: function () { ... query for your bookmarks ... }
}
this.push(myTest);
*/
var test = {
populate: function populate() {
// check initial size
var rootNode = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
false, false).root;
do_check_eq(rootNode.childCount, PLACES_ROOTS_COUNT );
rootNode.containerOpen = false;
var idx = PlacesUtils.bookmarks.DEFAULT_INDEX;
// create a root to be restore
this._restoreRootTitle = "restore root";
var restoreRootId = PlacesUtils.bookmarks
.createFolder(PlacesUtils.placesRootId,
this._restoreRootTitle, idx);
// add a test bookmark
this._restoreRootURI = uri("http://restore.uri");
PlacesUtils.bookmarks.insertBookmark(restoreRootId, this._restoreRootURI,
idx, "restore uri");
// add a test bookmark to be exclude
this._restoreRootExcludeURI = uri("http://exclude.uri");
var exItemId = PlacesUtils.bookmarks
.insertBookmark(restoreRootId,
this._restoreRootExcludeURI,
idx, "exclude uri");
// Annotate the bookmark for exclusion.
PlacesUtils.annotations.setItemAnnotation(exItemId,
EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
PlacesUtils.annotations.EXPIRE_NEVER);
// create a root to be exclude
this._excludeRootTitle = "exclude root";
this._excludeRootId = PlacesUtils.bookmarks
.createFolder(PlacesUtils.placesRootId,
this._excludeRootTitle, idx);
// Annotate the root for exclusion.
PlacesUtils.annotations.setItemAnnotation(this._excludeRootId,
EXCLUDE_FROM_BACKUP_ANNO, 1, 0,
PlacesUtils.annotations.EXPIRE_NEVER);
// add a test bookmark exclude by exclusion of its parent
PlacesUtils.bookmarks.insertBookmark(this._excludeRootId,
this._restoreRootExcludeURI,
idx, "exclude uri");
},
validate: function validate(aEmptyBookmarks) {
var rootNode = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
false, false).root;
if (!aEmptyBookmarks) {
// since restore does not remove backup exclude items both
// roots should still exist.
do_check_eq(rootNode.childCount, PLACES_ROOTS_COUNT + 2);
// open exclude root and check it still contains one item
var restoreRootIndex = PLACES_ROOTS_COUNT;
var excludeRootIndex = PLACES_ROOTS_COUNT+1;
var excludeRootNode = rootNode.getChild(excludeRootIndex);
do_check_eq(this._excludeRootTitle, excludeRootNode.title);
excludeRootNode.QueryInterface(Ci.nsINavHistoryQueryResultNode);
excludeRootNode.containerOpen = true;
do_check_eq(excludeRootNode.childCount, 1);
var excludeRootChildNode = excludeRootNode.getChild(0);
do_check_eq(excludeRootChildNode.uri, this._restoreRootExcludeURI.spec);
excludeRootNode.containerOpen = false;
}
else {
// exclude root should not exist anymore
do_check_eq(rootNode.childCount, PLACES_ROOTS_COUNT + 1);
restoreRootIndex = PLACES_ROOTS_COUNT;
}
var restoreRootNode = rootNode.getChild(restoreRootIndex);
do_check_eq(this._restoreRootTitle, restoreRootNode.title);
restoreRootNode.QueryInterface(Ci.nsINavHistoryQueryResultNode);
restoreRootNode.containerOpen = true;
do_check_eq(restoreRootNode.childCount, 1);
var restoreRootChildNode = restoreRootNode.getChild(0);
do_check_eq(restoreRootChildNode.uri, this._restoreRootURI.spec);
restoreRootNode.containerOpen = false;
rootNode.containerOpen = false;
}
}
function run_test() {
run_next_test();
}
add_task(function*() {
// make json file
let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
// populate db
test.populate();
yield BookmarkJSONUtils.exportToFile(jsonFile);
// restore json file
yield BookmarkJSONUtils.importFromFile(jsonFile, true);
// validate without removing all bookmarks
// restore do not remove backup exclude entries
test.validate(false);
// cleanup
yield PlacesUtils.bookmarks.eraseEverything();
// manually remove the excluded root
PlacesUtils.bookmarks.removeItem(test._excludeRootId);
// restore json file
yield BookmarkJSONUtils.importFromFile(jsonFile, true);
// validate after a complete bookmarks cleanup
test.validate(true);
// clean up
yield OS.File.remove(jsonFile);
});

View file

@ -0,0 +1,158 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
var tests = [];
/*
Backup/restore tests example:
var myTest = {
populate: function () { ... add bookmarks ... },
validate: function () { ... query for your bookmarks ... }
}
this.push(myTest);
*/
tests.push({
excludeItemsFromRestore: [],
populate: function populate() {
// check initial size
var rootNode = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
false, false).root;
do_check_eq(rootNode.childCount, 5);
// create a test root
this._folderTitle = "test folder";
this._folderId =
PlacesUtils.bookmarks.createFolder(PlacesUtils.placesRootId,
this._folderTitle,
PlacesUtils.bookmarks.DEFAULT_INDEX);
do_check_eq(rootNode.childCount, 6);
// add a tag
this._testURI = PlacesUtils._uri("http://test");
this._tags = ["a", "b"];
PlacesUtils.tagging.tagURI(this._testURI, this._tags);
// add a child to each root, including our test root
this._roots = [PlacesUtils.bookmarksMenuFolderId, PlacesUtils.toolbarFolderId,
PlacesUtils.unfiledBookmarksFolderId, PlacesUtils.mobileFolderId,
this._folderId];
this._roots.forEach(function(aRootId) {
// clean slate
PlacesUtils.bookmarks.removeFolderChildren(aRootId);
// add a test bookmark
PlacesUtils.bookmarks.insertBookmark(aRootId, this._testURI,
PlacesUtils.bookmarks.DEFAULT_INDEX, "test");
}, this);
// add a folder to exclude from replacing during restore
// this will still be present post-restore
var excludedFolderId =
PlacesUtils.bookmarks.createFolder(PlacesUtils.placesRootId,
"excluded",
PlacesUtils.bookmarks.DEFAULT_INDEX);
do_check_eq(rootNode.childCount, 7);
this.excludeItemsFromRestore.push(excludedFolderId);
// add a test bookmark to it
PlacesUtils.bookmarks.insertBookmark(excludedFolderId, this._testURI,
PlacesUtils.bookmarks.DEFAULT_INDEX, "test");
},
inbetween: function inbetween() {
// add some items that should be removed by the restore
// add a folder
this._litterTitle = "otter";
PlacesUtils.bookmarks.createFolder(PlacesUtils.placesRootId,
this._litterTitle, 0);
// add some tags
PlacesUtils.tagging.tagURI(this._testURI, ["c", "d"]);
},
validate: function validate() {
// validate tags restored
var tags = PlacesUtils.tagging.getTagsForURI(this._testURI);
// also validates that litter tags are gone
do_check_eq(this._tags.toString(), tags.toString());
var rootNode = PlacesUtils.getFolderContents(PlacesUtils.placesRootId,
false, false).root;
// validate litter is gone
do_check_neq(rootNode.getChild(0).title, this._litterTitle);
// test root count is the same
do_check_eq(rootNode.childCount, 7);
var foundTestFolder = 0;
for (var i = 0; i < rootNode.childCount; i++) {
var node = rootNode.getChild(i);
do_print("validating " + node.title);
if (node.itemId != PlacesUtils.tagsFolderId) {
if (node.title == this._folderTitle) {
// check the test folder's properties
do_check_eq(node.type, node.RESULT_TYPE_FOLDER);
do_check_eq(node.title, this._folderTitle);
foundTestFolder++;
}
// test contents
node.QueryInterface(Ci.nsINavHistoryContainerResultNode).containerOpen = true;
do_check_eq(node.childCount, 1);
var child = node.getChild(0);
do_check_true(PlacesUtils._uri(child.uri).equals(this._testURI));
// clean up
node.containerOpen = false;
}
}
do_check_eq(foundTestFolder, 1);
rootNode.containerOpen = false;
}
});
function run_test() {
run_next_test();
}
add_task(function* () {
// make json file
let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
// populate db
tests.forEach(function(aTest) {
aTest.populate();
// sanity
aTest.validate();
if (aTest.excludedItemsFromRestore)
excludedItemsFromRestore = excludedItems.concat(aTest.excludedItemsFromRestore);
});
yield BookmarkJSONUtils.exportToFile(jsonFile);
tests.forEach(function(aTest) {
aTest.inbetween();
});
// restore json file
yield BookmarkJSONUtils.importFromFile(jsonFile, true);
// validate
tests.forEach(function(aTest) {
aTest.validate();
});
// clean up
yield OS.File.remove(jsonFile);
});

View file

@ -0,0 +1,91 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
var tests = [];
/*
Backup/restore tests example:
var myTest = {
populate: function () { ... add bookmarks ... },
validate: function () { ... query for your bookmarks ... }
}
this.push(myTest);
*/
var quotesTest = {
_folderTitle: '"quoted folder"',
_folderId: null,
populate: function () {
this._folderId =
PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId,
this._folderTitle,
PlacesUtils.bookmarks.DEFAULT_INDEX);
},
clean: function () {
PlacesUtils.bookmarks.removeItem(this._folderId);
},
validate: function () {
var query = PlacesUtils.history.getNewQuery();
query.setFolders([PlacesUtils.bookmarks.toolbarFolder], 1);
var result = PlacesUtils.history.executeQuery(query, PlacesUtils.history.getNewQueryOptions());
var toolbar = result.root;
toolbar.containerOpen = true;
// test for our quoted folder
do_check_true(toolbar.childCount, 1);
var folderNode = toolbar.getChild(0);
do_check_eq(folderNode.type, folderNode.RESULT_TYPE_FOLDER);
do_check_eq(folderNode.title, this._folderTitle);
// clean up
toolbar.containerOpen = false;
}
}
tests.push(quotesTest);
function run_test() {
run_next_test();
}
add_task(function* () {
// make json file
let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
// populate db
tests.forEach(function(aTest) {
aTest.populate();
// sanity
aTest.validate();
});
// export json to file
yield BookmarkJSONUtils.exportToFile(jsonFile);
// clean
tests.forEach(function(aTest) {
aTest.clean();
});
// restore json file
yield BookmarkJSONUtils.importFromFile(jsonFile, true);
// validate
tests.forEach(function(aTest) {
aTest.validate();
});
// clean up
yield OS.File.remove(jsonFile);
});

View file

@ -0,0 +1,113 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
var tests = [];
// Get database connection
try {
var mDBConn = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.DBConnection;
}
catch (ex) {
do_throw("Could not get database connection\n");
}
/*
This test is:
- don't try to add invalid uri nodes to a JSON backup
*/
var invalidURITest = {
_itemTitle: "invalid uri",
_itemUrl: "http://test.mozilla.org/",
_itemId: null,
populate: function () {
// add a valid bookmark
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
PlacesUtils._uri(this._itemUrl),
PlacesUtils.bookmarks.DEFAULT_INDEX,
this._itemTitle);
// this bookmark will go corrupt
this._itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
PlacesUtils._uri(this._itemUrl),
PlacesUtils.bookmarks.DEFAULT_INDEX,
this._itemTitle);
},
clean: function () {
PlacesUtils.bookmarks.removeItem(this._itemId);
},
validate: function (aExpectValidItemsCount) {
var query = PlacesUtils.history.getNewQuery();
query.setFolders([PlacesUtils.bookmarks.toolbarFolder], 1);
var options = PlacesUtils.history.getNewQueryOptions();
var result = PlacesUtils.history.executeQuery(query, options);
var toolbar = result.root;
toolbar.containerOpen = true;
// test for our bookmark
do_check_eq(toolbar.childCount, aExpectValidItemsCount);
for (var i = 0; i < toolbar.childCount; i++) {
var folderNode = toolbar.getChild(0);
do_check_eq(folderNode.type, folderNode.RESULT_TYPE_URI);
do_check_eq(folderNode.title, this._itemTitle);
}
// clean up
toolbar.containerOpen = false;
}
}
tests.push(invalidURITest);
function run_test() {
run_next_test();
}
add_task(function*() {
// make json file
let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
// populate db
tests.forEach(function(aTest) {
aTest.populate();
// sanity
aTest.validate(2);
// Something in the code went wrong and we finish up losing the place, so
// the bookmark uri becomes null.
var sql = "UPDATE moz_bookmarks SET fk = 1337 WHERE id = ?1";
var stmt = mDBConn.createStatement(sql);
stmt.bindByIndex(0, aTest._itemId);
try {
stmt.execute();
} finally {
stmt.finalize();
}
});
yield BookmarkJSONUtils.exportToFile(jsonFile);
// clean
tests.forEach(function(aTest) {
aTest.clean();
});
// restore json file
try {
yield BookmarkJSONUtils.importFromFile(jsonFile, true);
} catch (ex) { do_throw("couldn't import the exported file: " + ex); }
// validate
tests.forEach(function(aTest) {
aTest.validate(1);
});
// clean up
yield OS.File.remove(jsonFile);
});

View file

@ -0,0 +1,131 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
var tests = [];
// Get database connection
try {
var mDBConn = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.DBConnection;
}
catch (ex) {
do_throw("Could not get database connection\n");
}
/*
This test is:
- don't block while doing backup and restore if tag containers contain
bogus items (separators, folders)
*/
var invalidTagChildTest = {
_itemTitle: "invalid uri",
_itemUrl: "http://test.mozilla.org/",
_itemId: -1,
_tag: "testTag",
_tagItemId: -1,
populate: function () {
// add a valid bookmark
this._itemId = PlacesUtils.bookmarks
.insertBookmark(PlacesUtils.toolbarFolderId,
PlacesUtils._uri(this._itemUrl),
PlacesUtils.bookmarks.DEFAULT_INDEX,
this._itemTitle);
// create a tag
PlacesUtils.tagging.tagURI(PlacesUtils._uri(this._itemUrl), [this._tag]);
// get tag folder id
var options = PlacesUtils.history.getNewQueryOptions();
var query = PlacesUtils.history.getNewQuery();
query.setFolders([PlacesUtils.bookmarks.tagsFolder], 1);
var result = PlacesUtils.history.executeQuery(query, options);
var tagRoot = result.root;
tagRoot.containerOpen = true;
do_check_eq(tagRoot.childCount, 1);
var tagNode = tagRoot.getChild(0)
.QueryInterface(Ci.nsINavHistoryContainerResultNode);
this._tagItemId = tagNode.itemId;
tagRoot.containerOpen = false;
// add a separator and a folder inside tag folder
PlacesUtils.bookmarks.insertSeparator(this._tagItemId,
PlacesUtils.bookmarks.DEFAULT_INDEX);
PlacesUtils.bookmarks.createFolder(this._tagItemId,
"test folder",
PlacesUtils.bookmarks.DEFAULT_INDEX);
// add a separator and a folder inside tag root
PlacesUtils.bookmarks.insertSeparator(PlacesUtils.bookmarks.tagsFolder,
PlacesUtils.bookmarks.DEFAULT_INDEX);
PlacesUtils.bookmarks.createFolder(PlacesUtils.bookmarks.tagsFolder,
"test tags root folder",
PlacesUtils.bookmarks.DEFAULT_INDEX);
},
clean: function () {
PlacesUtils.tagging.untagURI(PlacesUtils._uri(this._itemUrl), [this._tag]);
PlacesUtils.bookmarks.removeItem(this._itemId);
},
validate: function () {
var query = PlacesUtils.history.getNewQuery();
query.setFolders([PlacesUtils.bookmarks.toolbarFolder], 1);
var options = PlacesUtils.history.getNewQueryOptions();
var result = PlacesUtils.history.executeQuery(query, options);
var toolbar = result.root;
toolbar.containerOpen = true;
// test for our bookmark
do_check_eq(toolbar.childCount, 1);
for (var i = 0; i < toolbar.childCount; i++) {
var folderNode = toolbar.getChild(0);
do_check_eq(folderNode.type, folderNode.RESULT_TYPE_URI);
do_check_eq(folderNode.title, this._itemTitle);
}
toolbar.containerOpen = false;
// test for our tag
var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(this._itemUrl));
do_check_eq(tags.length, 1);
do_check_eq(tags[0], this._tag);
}
}
tests.push(invalidTagChildTest);
function run_test() {
run_next_test()
}
add_task(function* () {
let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
// populate db
tests.forEach(function(aTest) {
aTest.populate();
// sanity
aTest.validate();
});
yield BookmarkJSONUtils.exportToFile(jsonFile);
// clean
tests.forEach(function(aTest) {
aTest.clean();
});
// restore json file
yield BookmarkJSONUtils.importFromFile(jsonFile, true);
// validate
tests.forEach(function(aTest) {
aTest.validate();
});
// clean up
yield OS.File.remove(jsonFile);
});

View file

@ -0,0 +1,124 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
// Since PlacesBackups.getbackupFiles() is a lazy getter, these tests must
// run in the given order, to avoid making it out-of-sync.
add_task(function* check_max_backups_is_respected() {
// Get bookmarkBackups directory
let backupFolder = yield PlacesBackups.getBackupFolder();
// Create 2 json dummy backups in the past.
let oldJsonPath = OS.Path.join(backupFolder, "bookmarks-2008-01-01.json");
let oldJsonFile = yield OS.File.open(oldJsonPath, { truncate: true });
oldJsonFile.close();
do_check_true(yield OS.File.exists(oldJsonPath));
let jsonPath = OS.Path.join(backupFolder, "bookmarks-2008-01-31.json");
let jsonFile = yield OS.File.open(jsonPath, { truncate: true });
jsonFile.close();
do_check_true(yield OS.File.exists(jsonPath));
// Export bookmarks to JSON.
// Allow 2 backups, the older one should be removed.
yield PlacesBackups.create(2);
let count = 0;
let lastBackupPath = null;
let iterator = new OS.File.DirectoryIterator(backupFolder);
try {
yield iterator.forEach(aEntry => {
count++;
if (PlacesBackups.filenamesRegex.test(aEntry.name))
lastBackupPath = aEntry.path;
});
} finally {
iterator.close();
}
do_check_eq(count, 2);
do_check_neq(lastBackupPath, null);
do_check_false(yield OS.File.exists(oldJsonPath));
do_check_true(yield OS.File.exists(jsonPath));
});
add_task(function* check_max_backups_greater_than_backups() {
// Get bookmarkBackups directory
let backupFolder = yield PlacesBackups.getBackupFolder();
// Export bookmarks to JSON.
// Allow 3 backups, none should be removed.
yield PlacesBackups.create(3);
let count = 0;
let lastBackupPath = null;
let iterator = new OS.File.DirectoryIterator(backupFolder);
try {
yield iterator.forEach(aEntry => {
count++;
if (PlacesBackups.filenamesRegex.test(aEntry.name))
lastBackupPath = aEntry.path;
});
} finally {
iterator.close();
}
do_check_eq(count, 2);
do_check_neq(lastBackupPath, null);
});
add_task(function* check_max_backups_null() {
// Get bookmarkBackups directory
let backupFolder = yield PlacesBackups.getBackupFolder();
// Export bookmarks to JSON.
// Allow infinite backups, none should be removed, a new one is not created
// since one for today already exists.
yield PlacesBackups.create(null);
let count = 0;
let lastBackupPath = null;
let iterator = new OS.File.DirectoryIterator(backupFolder);
try {
yield iterator.forEach(aEntry => {
count++;
if (PlacesBackups.filenamesRegex.test(aEntry.name))
lastBackupPath = aEntry.path;
});
} finally {
iterator.close();
}
do_check_eq(count, 2);
do_check_neq(lastBackupPath, null);
});
add_task(function* check_max_backups_undefined() {
// Get bookmarkBackups directory
let backupFolder = yield PlacesBackups.getBackupFolder();
// Export bookmarks to JSON.
// Allow infinite backups, none should be removed, a new one is not created
// since one for today already exists.
yield PlacesBackups.create();
let count = 0;
let lastBackupPath = null;
let iterator = new OS.File.DirectoryIterator(backupFolder);
try {
yield iterator.forEach(aEntry => {
count++;
if (PlacesBackups.filenamesRegex.test(aEntry.name))
lastBackupPath = aEntry.path;
});
} finally {
iterator.close();
}
do_check_eq(count, 2);
do_check_neq(lastBackupPath, null);
});
function run_test() {
run_next_test();
}

View file

@ -0,0 +1,56 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
function run_test() {
do_test_pending();
Task.spawn(function*() {
let backupFolder = yield PlacesBackups.getBackupFolder();
let bookmarksBackupDir = new FileUtils.File(backupFolder);
// Remove all files from backups folder.
let files = bookmarksBackupDir.directoryEntries;
while (files.hasMoreElements()) {
let entry = files.getNext().QueryInterface(Ci.nsIFile);
entry.remove(false);
}
// Create a json dummy backup in the future.
let dateObj = new Date();
dateObj.setYear(dateObj.getFullYear() + 1);
let name = PlacesBackups.getFilenameForDate(dateObj);
do_check_eq(name, "bookmarks-" + PlacesBackups.toISODateString(dateObj) + ".json");
files = bookmarksBackupDir.directoryEntries;
while (files.hasMoreElements()) {
let entry = files.getNext().QueryInterface(Ci.nsIFile);
if (PlacesBackups.filenamesRegex.test(entry.leafName))
entry.remove(false);
}
let futureBackupFile = bookmarksBackupDir.clone();
futureBackupFile.append(name);
futureBackupFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, 0o600);
do_check_true(futureBackupFile.exists());
do_check_eq((yield PlacesBackups.getBackupFiles()).length, 0);
yield PlacesBackups.create();
// Check that a backup for today has been created.
do_check_eq((yield PlacesBackups.getBackupFiles()).length, 1);
let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
do_check_neq(mostRecentBackupFile, null);
do_check_true(PlacesBackups.filenamesRegex.test(OS.Path.basename(mostRecentBackupFile)));
// Check that future backup has been removed.
do_check_false(futureBackupFile.exists());
// Cleanup.
mostRecentBackupFile = new FileUtils.File(mostRecentBackupFile);
mostRecentBackupFile.remove(false);
do_check_false(mostRecentBackupFile.exists());
do_test_finished()
});
}

View file

@ -0,0 +1,56 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function run_test() {
/**
* Requests information to the service, so that bookmark's data is cached.
* @param aItemId
* Id of the bookmark to be cached.
*/
function forceBookmarkCaching(aItemId) {
PlacesUtils.bookmarks.getFolderIdForItem(aItemId);
}
let observer = {
onBeginUpdateBatch: () => forceBookmarkCaching(itemId1),
onEndUpdateBatch: () => forceBookmarkCaching(itemId1),
onItemAdded: forceBookmarkCaching,
onItemChanged: forceBookmarkCaching,
onItemMoved: forceBookmarkCaching,
onItemRemoved: function(id) {
try {
forceBookmarkCaching(id);
do_throw("trying to fetch a removed bookmark should throw");
} catch (ex) {}
},
onItemVisited: forceBookmarkCaching,
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
};
PlacesUtils.bookmarks.addObserver(observer, false);
let folderId1 = PlacesUtils.bookmarks
.createFolder(PlacesUtils.bookmarksMenuFolderId,
"Bookmarks",
PlacesUtils.bookmarks.DEFAULT_INDEX);
let itemId1 = PlacesUtils.bookmarks
.insertBookmark(folderId1,
NetUtil.newURI("http:/www.wired.com/wiredscience"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"Wired Science");
PlacesUtils.bookmarks.removeItem(folderId1);
let folderId2 = PlacesUtils.bookmarks
.createFolder(PlacesUtils.bookmarksMenuFolderId,
"Science",
PlacesUtils.bookmarks.DEFAULT_INDEX);
let folderId3 = PlacesUtils.bookmarks
.createFolder(folderId2,
"Blogs",
PlacesUtils.bookmarks.DEFAULT_INDEX);
// Check title is correctly reported.
do_check_eq(PlacesUtils.bookmarks.getItemTitle(folderId3), "Blogs");
do_check_eq(PlacesUtils.bookmarks.getItemTitle(folderId2), "Science");
PlacesUtils.bookmarks.removeObserver(observer, false);
}

View file

@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
function run_test() {
/**
* Requests information to the service, so that bookmark's data is cached.
* @param aItemId
* Id of the bookmark to be cached.
*/
function forceBookmarkCaching(aItemId) {
let parent = PlacesUtils.bookmarks.getFolderIdForItem(aItemId);
PlacesUtils.bookmarks.getFolderIdForItem(parent);
}
let observer = {
onBeginUpdateBatch: () => forceBookmarkCaching(itemId1),
onEndUpdateBatch: () => forceBookmarkCaching(itemId1),
onItemAdded: forceBookmarkCaching,
onItemChanged: forceBookmarkCaching,
onItemMoved: forceBookmarkCaching,
onItemRemoved: function (id) {
try {
forceBookmarkCaching(id);
do_throw("trying to fetch a removed bookmark should throw");
} catch (ex) {}
},
onItemVisited: forceBookmarkCaching,
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
};
PlacesUtils.bookmarks.addObserver(observer, false);
let folder1 = PlacesUtils.bookmarks
.createFolder(PlacesUtils.bookmarksMenuFolderId,
"Folder1",
PlacesUtils.bookmarks.DEFAULT_INDEX);
let folder2 = PlacesUtils.bookmarks
.createFolder(folder1,
"Folder2",
PlacesUtils.bookmarks.DEFAULT_INDEX);
PlacesUtils.bookmarks.insertBookmark(folder2,
NetUtil.newURI("http://mozilla.org/"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"Mozilla");
PlacesUtils.bookmarks.removeFolderChildren(folder1);
// Check title is correctly reported.
do_check_eq(PlacesUtils.bookmarks.getItemTitle(folder1), "Folder1");
try {
PlacesUtils.bookmarks.getItemTitle(folder2);
do_throw("trying to fetch a removed bookmark should throw");
} catch (ex) {}
PlacesUtils.bookmarks.removeObserver(observer, false);
}

View file

@ -0,0 +1,59 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* Checks that automatically created bookmark backups are discarded if they are
* duplicate of an existing ones.
*/
function run_test() {
run_next_test();
}
add_task(function*() {
// Create a backup for yesterday in the backups folder.
let backupFolder = yield PlacesBackups.getBackupFolder();
let dateObj = new Date();
dateObj.setDate(dateObj.getDate() - 1);
let oldBackupName = PlacesBackups.getFilenameForDate(dateObj);
let oldBackup = OS.Path.join(backupFolder, oldBackupName);
let {count: count, hash: hash} = yield BookmarkJSONUtils.exportToFile(oldBackup);
do_check_true(count > 0);
do_check_eq(hash.length, 24);
oldBackupName = oldBackupName.replace(/\.json/, "_" + count + "_" + hash + ".json");
yield OS.File.move(oldBackup, OS.Path.join(backupFolder, oldBackupName));
// Create a backup.
// This should just rename the existing backup, so in the end there should be
// only one backup with today's date.
yield PlacesBackups.create();
// Get the hash of the generated backup
let backupFiles = yield PlacesBackups.getBackupFiles();
do_check_eq(backupFiles.length, 1);
let matches = OS.Path.basename(backupFiles[0]).match(PlacesBackups.filenamesRegex);
do_check_eq(matches[1], PlacesBackups.toISODateString(new Date()));
do_check_eq(matches[2], count);
do_check_eq(matches[3], hash);
// Add a bookmark and create another backup.
let bookmarkId = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarks.bookmarksMenuFolder,
uri("http://foo.com"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"foo");
// We must enforce a backup since one for today already exists. The forced
// backup will replace the existing one.
yield PlacesBackups.create(undefined, true);
do_check_eq(backupFiles.length, 1);
recentBackup = yield PlacesBackups.getMostRecentBackup();
do_check_neq(recentBackup, OS.Path.join(backupFolder, oldBackupName));
matches = OS.Path.basename(recentBackup).match(PlacesBackups.filenamesRegex);
do_check_eq(matches[1], PlacesBackups.toISODateString(new Date()));
do_check_eq(matches[2], count + 1);
do_check_neq(matches[3], hash);
// Clean up
PlacesUtils.bookmarks.removeItem(bookmarkId);
yield PlacesBackups.create(0);
});

View file

@ -0,0 +1,57 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
function run_test() {
run_next_test();
}
add_task(function* compress_bookmark_backups_test() {
// Check for jsonlz4 extension
let todayFilename = PlacesBackups.getFilenameForDate(new Date(2014, 4, 15), true);
do_check_eq(todayFilename, "bookmarks-2014-05-15.jsonlz4");
yield PlacesBackups.create();
// Check that a backup for today has been created and the regex works fine for lz4.
do_check_eq((yield PlacesBackups.getBackupFiles()).length, 1);
let mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
do_check_neq(mostRecentBackupFile, null);
do_check_true(PlacesBackups.filenamesRegex.test(OS.Path.basename(mostRecentBackupFile)));
// The most recent backup file has to be removed since saveBookmarksToJSONFile
// will otherwise over-write the current backup, since it will be made on the
// same date
yield OS.File.remove(mostRecentBackupFile);
do_check_false((yield OS.File.exists(mostRecentBackupFile)));
// Check that, if the user created a custom backup out of the default
// backups folder, it gets copied (compressed) into it.
let jsonFile = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.json");
yield PlacesBackups.saveBookmarksToJSONFile(jsonFile);
do_check_eq((yield PlacesBackups.getBackupFiles()).length, 1);
// Check if import works from lz4 compressed json
let uri = NetUtil.newURI("http://www.mozilla.org/en-US/");
let bm = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
uri,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"bookmark");
// Force create a compressed backup, Remove the bookmark, the restore the backup
yield PlacesBackups.create(undefined, true);
let recentBackup = yield PlacesBackups.getMostRecentBackup();
PlacesUtils.bookmarks.removeItem(bm);
yield BookmarkJSONUtils.importFromFile(recentBackup, true);
let root = PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId).root;
let node = root.getChild(0);
do_check_eq(node.uri, uri.spec);
root.containerOpen = false;
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
// Cleanup.
yield OS.File.remove(jsonFile);
});

View file

@ -0,0 +1,57 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* To confirm that metadata i.e. bookmark count is set and retrieved for
* automatic backups.
*/
function run_test() {
run_next_test();
}
add_task(function* test_saveBookmarksToJSONFile_and_create()
{
// Add a bookmark
let uri = NetUtil.newURI("http://getfirefox.com/");
let bookmarkId =
PlacesUtils.bookmarks.insertBookmark(
PlacesUtils.unfiledBookmarksFolderId, uri,
PlacesUtils.bookmarks.DEFAULT_INDEX, "Get Firefox!");
// Test saveBookmarksToJSONFile()
let backupFile = FileUtils.getFile("TmpD", ["bookmarks.json"]);
backupFile.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, parseInt("0600", 8));
let nodeCount = yield PlacesBackups.saveBookmarksToJSONFile(backupFile, true);
do_check_true(nodeCount > 0);
do_check_true(backupFile.exists());
do_check_eq(backupFile.leafName, "bookmarks.json");
// Ensure the backup would be copied to our backups folder when the original
// backup is saved somewhere else.
let recentBackup = yield PlacesBackups.getMostRecentBackup();
let matches = OS.Path.basename(recentBackup).match(PlacesBackups.filenamesRegex);
do_check_eq(matches[2], nodeCount);
do_check_eq(matches[3].length, 24);
// Clear all backups in our backups folder.
yield PlacesBackups.create(0);
do_check_eq((yield PlacesBackups.getBackupFiles()).length, 0);
// Test create() which saves bookmarks with metadata on the filename.
yield PlacesBackups.create();
do_check_eq((yield PlacesBackups.getBackupFiles()).length, 1);
mostRecentBackupFile = yield PlacesBackups.getMostRecentBackup();
do_check_neq(mostRecentBackupFile, null);
matches = OS.Path.basename(recentBackup).match(PlacesBackups.filenamesRegex);
do_check_eq(matches[2], nodeCount);
do_check_eq(matches[3].length, 24);
// Cleanup
backupFile.remove(false);
yield PlacesBackups.create(0);
PlacesUtils.bookmarks.removeItem(bookmarkId);
});

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* Checks that backups properly include all of the bookmarks if the hierarchy
* in the database is unordered so that a hierarchy is defined before its
* ancestor in the bookmarks table.
*/
function run_test() {
run_next_test();
}
add_task(function*() {
let bm = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
NetUtil.newURI("http://mozilla.org/"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"bookmark");
let f2 = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId, "f2",
PlacesUtils.bookmarks.DEFAULT_INDEX);
PlacesUtils.bookmarks.moveItem(bm, f2, PlacesUtils.bookmarks.DEFAULT_INDEX);
let f1 = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId, "f1",
PlacesUtils.bookmarks.DEFAULT_INDEX);
PlacesUtils.bookmarks.moveItem(f2, f1, PlacesUtils.bookmarks.DEFAULT_INDEX);
// Create a backup.
yield PlacesBackups.create();
// Remove the bookmarks, then restore the backup.
PlacesUtils.bookmarks.removeItem(f1);
yield BookmarkJSONUtils.importFromFile((yield PlacesBackups.getMostRecentBackup()), true);
do_print("Checking first level");
let root = PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId).root;
let level1 = root.getChild(0);
do_check_eq(level1.title, "f1");
do_print("Checking second level");
PlacesUtils.asContainer(level1).containerOpen = true
let level2 = level1.getChild(0);
do_check_eq(level2.title, "f2");
do_print("Checking bookmark");
PlacesUtils.asContainer(level2).containerOpen = true
let bookmark = level2.getChild(0);
do_check_eq(bookmark.title, "bookmark");
level2.containerOpen = false;
level1.containerOpen = false;
root.containerOpen = false;
});

View file

@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* Checks that we don't encodeURI twice when creating bookmarks.html.
*/
function run_test() {
run_next_test();
}
add_task(function* () {
let uri = NetUtil.newURI("http://bt.ktxp.com/search.php?keyword=%E5%A6%84%E6%83%B3%E5%AD%A6%E7%94%9F%E4%BC%9A");
let bm = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
uri,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"bookmark");
let file = OS.Path.join(OS.Constants.Path.profileDir, "bookmarks.exported.997030.html");
if ((yield OS.File.exists(file))) {
yield OS.File.remove(file);
}
yield BookmarkHTMLUtils.exportToFile(file);
// Remove the bookmarks, then restore the backup.
PlacesUtils.bookmarks.removeItem(bm);
yield BookmarkHTMLUtils.importFromFile(file, true);
do_print("Checking first level");
let root = PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId).root;
let node = root.getChild(0);
do_check_eq(node.uri, uri.spec);
root.containerOpen = false;
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
});

View file

@ -0,0 +1,177 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* This test checks that bookmarks service is correctly forwarding async
* events like visit or favicon additions. */
const NOW = Date.now() * 1000;
var observer = {
bookmarks: [],
observedBookmarks: 0,
observedVisitId: 0,
deferred: null,
/**
* Returns a promise that is resolved when the observer determines that the
* test can continue. This is required rather than calling run_next_test
* directly in the observer because there are cases where we must wait for
* other asynchronous events to be completed in addition to this.
*/
setupCompletionPromise: function ()
{
this.observedBookmarks = 0;
this.deferred = Promise.defer();
return this.deferred.promise;
},
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onItemAdded: function () {},
onItemRemoved: function () {},
onItemMoved: function () {},
onItemChanged: function(aItemId, aProperty, aIsAnnotation, aNewValue,
aLastModified, aItemType)
{
do_print("Check that we got the correct change information.");
do_check_neq(this.bookmarks.indexOf(aItemId), -1);
if (aProperty == "favicon") {
do_check_false(aIsAnnotation);
do_check_eq(aNewValue, SMALLPNG_DATA_URI.spec);
do_check_eq(aLastModified, 0);
do_check_eq(aItemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
}
else if (aProperty == "cleartime") {
do_check_false(aIsAnnotation);
do_check_eq(aNewValue, "");
do_check_eq(aLastModified, 0);
do_check_eq(aItemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
}
else {
do_throw("Unexpected property change " + aProperty);
}
if (++this.observedBookmarks == this.bookmarks.length) {
this.deferred.resolve();
}
},
onItemVisited: function(aItemId, aVisitId, aTime)
{
do_print("Check that we got the correct visit information.");
do_check_neq(this.bookmarks.indexOf(aItemId), -1);
this.observedVisitId = aVisitId;
do_check_eq(aTime, NOW);
if (++this.observedBookmarks == this.bookmarks.length) {
this.deferred.resolve();
}
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsINavBookmarkObserver,
])
};
PlacesUtils.bookmarks.addObserver(observer, false);
add_task(function* test_add_visit()
{
let observerPromise = observer.setupCompletionPromise();
// Add a visit to the bookmark and wait for the observer.
let visitId;
let deferUpdatePlaces = Promise.defer();
PlacesUtils.asyncHistory.updatePlaces({
uri: NetUtil.newURI("http://book.ma.rk/"),
visits: [{ transitionType: TRANSITION_TYPED, visitDate: NOW }]
}, {
handleError: function TAV_handleError() {
deferUpdatePlaces.reject(new Error("Unexpected error in adding visit."));
},
handleResult: function (aPlaceInfo) {
visitId = aPlaceInfo.visits[0].visitId;
},
handleCompletion: function TAV_handleCompletion() {
deferUpdatePlaces.resolve();
}
});
// Wait for both the observer and the asynchronous update, in any order.
yield deferUpdatePlaces.promise;
yield observerPromise;
// Check that both asynchronous results are consistent.
do_check_eq(observer.observedVisitId, visitId);
});
add_task(function* test_add_icon()
{
let observerPromise = observer.setupCompletionPromise();
PlacesUtils.favicons.setAndFetchFaviconForPage(NetUtil.newURI("http://book.ma.rk/"),
SMALLPNG_DATA_URI, true,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null,
Services.scriptSecurityManager.getSystemPrincipal());
yield observerPromise;
});
add_task(function* test_remove_page()
{
let observerPromise = observer.setupCompletionPromise();
PlacesUtils.history.removePage(NetUtil.newURI("http://book.ma.rk/"));
yield observerPromise;
});
add_task(function cleanup()
{
PlacesUtils.bookmarks.removeObserver(observer, false);
});
add_task(function* shutdown()
{
// Check that async observers don't try to create async statements after
// shutdown. That would cause assertions, since the async thread is gone
// already. Note that in such a case the notifications are not fired, so we
// cannot test for them.
// Put an history notification that triggers AsyncGetBookmarksForURI between
// asyncClose() and the actual connection closing. Enqueuing a main-thread
// event just after places-will-close-connection should ensure it runs before
// places-connection-closed.
// Notice this code is not using helpers cause it depends on a very specific
// order, a change in the helpers code could make this test useless.
let deferred = Promise.defer();
Services.obs.addObserver(function onNotification() {
Services.obs.removeObserver(onNotification, "places-will-close-connection");
do_check_true(true, "Observed fake places shutdown");
Services.tm.mainThread.dispatch(() => {
// WARNING: this is very bad, never use out of testing code.
PlacesUtils.bookmarks.QueryInterface(Ci.nsINavHistoryObserver)
.onPageChanged(NetUtil.newURI("http://book.ma.rk/"),
Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON,
"test", "test");
deferred.resolve(promiseTopicObserved("places-connection-closed"));
}, Ci.nsIThread.DISPATCH_NORMAL);
}, "places-will-close-connection", false);
shutdownPlaces();
yield deferred.promise;
});
function run_test()
{
// Add multiple bookmarks to the same uri.
observer.bookmarks.push(
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
NetUtil.newURI("http://book.ma.rk/"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"Bookmark")
);
observer.bookmarks.push(
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId,
NetUtil.newURI("http://book.ma.rk/"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"Bookmark")
);
run_next_test();
}

View file

@ -0,0 +1,124 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
* This Source Code Form is subject to the terms of the Mozilla Public
* 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 NUM_BOOKMARKS = 20;
const NUM_SEPARATORS = 5;
const NUM_FOLDERS = 10;
const NUM_ITEMS = NUM_BOOKMARKS + NUM_SEPARATORS + NUM_FOLDERS;
const MIN_RAND = -5;
const MAX_RAND = 40;
var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
function check_contiguous_indexes(aBookmarks) {
var indexes = [];
aBookmarks.forEach(function(aBookmarkId) {
let bmIndex = bs.getItemIndex(aBookmarkId);
dump("Index: " + bmIndex + "\n");
dump("Checking duplicates\n");
do_check_eq(indexes.indexOf(bmIndex), -1);
dump("Checking out of range, found " + aBookmarks.length + " items\n");
do_check_true(bmIndex >= 0 && bmIndex < aBookmarks.length);
indexes.push(bmIndex);
});
dump("Checking all valid indexes have been used\n");
do_check_eq(indexes.length, aBookmarks.length);
}
// main
function run_test() {
var bookmarks = [];
// Insert bookmarks with random indexes.
for (let i = 0; bookmarks.length < NUM_BOOKMARKS; i++) {
let randIndex = Math.round(MIN_RAND + (Math.random() * (MAX_RAND - MIN_RAND)));
try {
let id = bs.insertBookmark(bs.unfiledBookmarksFolder,
uri("http://" + i + ".mozilla.org/"),
randIndex, "Test bookmark " + i);
if (randIndex < -1)
do_throw("Creating a bookmark at an invalid index should throw");
bookmarks.push(id);
}
catch (ex) {
if (randIndex >= -1)
do_throw("Creating a bookmark at a valid index should not throw");
}
}
check_contiguous_indexes(bookmarks);
// Insert separators with random indexes.
for (let i = 0; bookmarks.length < NUM_BOOKMARKS + NUM_SEPARATORS; i++) {
let randIndex = Math.round(MIN_RAND + (Math.random() * (MAX_RAND - MIN_RAND)));
try {
let id = bs.insertSeparator(bs.unfiledBookmarksFolder, randIndex);
if (randIndex < -1)
do_throw("Creating a separator at an invalid index should throw");
bookmarks.push(id);
}
catch (ex) {
if (randIndex >= -1)
do_throw("Creating a separator at a valid index should not throw");
}
}
check_contiguous_indexes(bookmarks);
// Insert folders with random indexes.
for (let i = 0; bookmarks.length < NUM_ITEMS; i++) {
let randIndex = Math.round(MIN_RAND + (Math.random() * (MAX_RAND - MIN_RAND)));
try {
let id = bs.createFolder(bs.unfiledBookmarksFolder,
"Test folder " + i, randIndex);
if (randIndex < -1)
do_throw("Creating a folder at an invalid index should throw");
bookmarks.push(id);
}
catch (ex) {
if (randIndex >= -1)
do_throw("Creating a folder at a valid index should not throw");
}
}
check_contiguous_indexes(bookmarks);
// Execute some random bookmark delete.
for (let i = 0; i < Math.ceil(NUM_ITEMS / 4); i++) {
let id = bookmarks.splice(Math.floor(Math.random() * bookmarks.length), 1);
dump("Removing item with id " + id + "\n");
bs.removeItem(id);
}
check_contiguous_indexes(bookmarks);
// Execute some random bookmark move. This will also try to move it to
// invalid index values.
for (let i = 0; i < Math.ceil(NUM_ITEMS / 4); i++) {
let randIndex = Math.floor(Math.random() * bookmarks.length);
let id = bookmarks[randIndex];
let newIndex = Math.round(MIN_RAND + (Math.random() * (MAX_RAND - MIN_RAND)));
dump("Moving item with id " + id + " to index " + newIndex + "\n");
try {
bs.moveItem(id, bs.unfiledBookmarksFolder, newIndex);
if (newIndex < -1)
do_throw("Moving an item to a negative index should throw\n");
}
catch (ex) {
if (newIndex >= -1)
do_throw("Moving an item to a valid index should not throw\n");
}
}
check_contiguous_indexes(bookmarks);
// Ensure setItemIndex throws if we pass it a negative index.
try {
bs.setItemIndex(bookmarks[0], -1);
do_throw("setItemIndex should throw for a negative index");
} catch (ex) {}
// Ensure setItemIndex throws if we pass it a bad itemId.
try {
bs.setItemIndex(0, 5);
do_throw("setItemIndex should throw for a bad itemId");
} catch (ex) {}
}

View file

@ -0,0 +1,718 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
var bs = PlacesUtils.bookmarks;
var hs = PlacesUtils.history;
var anno = PlacesUtils.annotations;
var bookmarksObserver = {
onBeginUpdateBatch: function() {
this._beginUpdateBatch = true;
},
onEndUpdateBatch: function() {
this._endUpdateBatch = true;
},
onItemAdded: function(id, folder, index, itemType, uri, title, dateAdded,
guid) {
this._itemAddedId = id;
this._itemAddedParent = folder;
this._itemAddedIndex = index;
this._itemAddedURI = uri;
this._itemAddedTitle = title;
// Ensure that we've created a guid for this item.
let stmt = DBConn().createStatement(
`SELECT guid
FROM moz_bookmarks
WHERE id = :item_id`
);
stmt.params.item_id = id;
do_check_true(stmt.executeStep());
do_check_false(stmt.getIsNull(0));
do_check_valid_places_guid(stmt.row.guid);
do_check_eq(stmt.row.guid, guid);
stmt.finalize();
},
onItemRemoved: function(id, folder, index, itemType) {
this._itemRemovedId = id;
this._itemRemovedFolder = folder;
this._itemRemovedIndex = index;
},
onItemChanged: function(id, property, isAnnotationProperty, value,
lastModified, itemType, parentId, guid, parentGuid,
oldValue) {
this._itemChangedId = id;
this._itemChangedProperty = property;
this._itemChanged_isAnnotationProperty = isAnnotationProperty;
this._itemChangedValue = value;
this._itemChangedOldValue = oldValue;
},
onItemVisited: function(id, visitID, time) {
this._itemVisitedId = id;
this._itemVisitedVistId = visitID;
this._itemVisitedTime = time;
},
onItemMoved: function(id, oldParent, oldIndex, newParent, newIndex,
itemType) {
this._itemMovedId = id
this._itemMovedOldParent = oldParent;
this._itemMovedOldIndex = oldIndex;
this._itemMovedNewParent = newParent;
this._itemMovedNewIndex = newIndex;
},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsINavBookmarkObserver,
])
};
// Get bookmarks menu folder id.
var root = bs.bookmarksMenuFolder;
// Index at which items should begin.
var bmStartIndex = 0;
function run_test() {
run_next_test();
}
add_task(function* test_bookmarks() {
bs.addObserver(bookmarksObserver, false);
// test special folders
do_check_true(bs.placesRoot > 0);
do_check_true(bs.bookmarksMenuFolder > 0);
do_check_true(bs.tagsFolder > 0);
do_check_true(bs.toolbarFolder > 0);
do_check_true(bs.unfiledBookmarksFolder > 0);
// test getFolderIdForItem() with bogus item id will throw
try {
bs.getFolderIdForItem(0);
do_throw("getFolderIdForItem accepted bad input");
} catch (ex) {}
// test getFolderIdForItem() with bogus item id will throw
try {
bs.getFolderIdForItem(-1);
do_throw("getFolderIdForItem accepted bad input");
} catch (ex) {}
// test root parentage
do_check_eq(bs.getFolderIdForItem(bs.bookmarksMenuFolder), bs.placesRoot);
do_check_eq(bs.getFolderIdForItem(bs.tagsFolder), bs.placesRoot);
do_check_eq(bs.getFolderIdForItem(bs.toolbarFolder), bs.placesRoot);
do_check_eq(bs.getFolderIdForItem(bs.unfiledBookmarksFolder), bs.placesRoot);
// create a folder to hold all the tests
// this makes the tests more tolerant of changes to default_places.html
let testRoot = bs.createFolder(root, "places bookmarks xpcshell tests",
bs.DEFAULT_INDEX);
do_check_eq(bookmarksObserver._itemAddedId, testRoot);
do_check_eq(bookmarksObserver._itemAddedParent, root);
do_check_eq(bookmarksObserver._itemAddedIndex, bmStartIndex);
do_check_eq(bookmarksObserver._itemAddedURI, null);
let testStartIndex = 0;
// test getItemIndex for folders
do_check_eq(bs.getItemIndex(testRoot), bmStartIndex);
// test getItemType for folders
do_check_eq(bs.getItemType(testRoot), bs.TYPE_FOLDER);
// insert a bookmark.
// the time before we insert, in microseconds
let beforeInsert = Date.now() * 1000;
do_check_true(beforeInsert > 0);
let newId = bs.insertBookmark(testRoot, uri("http://google.com/"),
bs.DEFAULT_INDEX, "");
do_check_eq(bookmarksObserver._itemAddedId, newId);
do_check_eq(bookmarksObserver._itemAddedParent, testRoot);
do_check_eq(bookmarksObserver._itemAddedIndex, testStartIndex);
do_check_true(bookmarksObserver._itemAddedURI.equals(uri("http://google.com/")));
do_check_eq(bs.getBookmarkURI(newId).spec, "http://google.com/");
let dateAdded = bs.getItemDateAdded(newId);
// dateAdded can equal beforeInsert
do_check_true(is_time_ordered(beforeInsert, dateAdded));
// after just inserting, modified should not be set
let lastModified = bs.getItemLastModified(newId);
do_check_eq(lastModified, dateAdded);
// The time before we set the title, in microseconds.
let beforeSetTitle = Date.now() * 1000;
do_check_true(beforeSetTitle >= beforeInsert);
// Workaround possible VM timers issues moving lastModified and dateAdded
// to the past.
lastModified -= 1000;
bs.setItemLastModified(newId, lastModified);
dateAdded -= 1000;
bs.setItemDateAdded(newId, dateAdded);
// set bookmark title
bs.setItemTitle(newId, "Google");
do_check_eq(bookmarksObserver._itemChangedId, newId);
do_check_eq(bookmarksObserver._itemChangedProperty, "title");
do_check_eq(bookmarksObserver._itemChangedValue, "Google");
// check that dateAdded hasn't changed
let dateAdded2 = bs.getItemDateAdded(newId);
do_check_eq(dateAdded2, dateAdded);
// check lastModified after we set the title
let lastModified2 = bs.getItemLastModified(newId);
do_print("test setItemTitle");
do_print("dateAdded = " + dateAdded);
do_print("beforeSetTitle = " + beforeSetTitle);
do_print("lastModified = " + lastModified);
do_print("lastModified2 = " + lastModified2);
do_check_true(is_time_ordered(lastModified, lastModified2));
do_check_true(is_time_ordered(dateAdded, lastModified2));
// get item title
let title = bs.getItemTitle(newId);
do_check_eq(title, "Google");
// test getItemType for bookmarks
do_check_eq(bs.getItemType(newId), bs.TYPE_BOOKMARK);
// get item title bad input
try {
bs.getItemTitle(-3);
do_throw("getItemTitle accepted bad input");
} catch (ex) {}
// get the folder that the bookmark is in
let folderId = bs.getFolderIdForItem(newId);
do_check_eq(folderId, testRoot);
// test getItemIndex for bookmarks
do_check_eq(bs.getItemIndex(newId), testStartIndex);
// create a folder at a specific index
let workFolder = bs.createFolder(testRoot, "Work", 0);
do_check_eq(bookmarksObserver._itemAddedId, workFolder);
do_check_eq(bookmarksObserver._itemAddedParent, testRoot);
do_check_eq(bookmarksObserver._itemAddedIndex, 0);
do_check_eq(bookmarksObserver._itemAddedURI, null);
do_check_eq(bs.getItemTitle(workFolder), "Work");
bs.setItemTitle(workFolder, "Work #");
do_check_eq(bs.getItemTitle(workFolder), "Work #");
// add item into subfolder, specifying index
let newId2 = bs.insertBookmark(workFolder,
uri("http://developer.mozilla.org/"),
0, "");
do_check_eq(bookmarksObserver._itemAddedId, newId2);
do_check_eq(bookmarksObserver._itemAddedParent, workFolder);
do_check_eq(bookmarksObserver._itemAddedIndex, 0);
// change item
bs.setItemTitle(newId2, "DevMo");
do_check_eq(bookmarksObserver._itemChangedProperty, "title");
// insert item into subfolder
let newId3 = bs.insertBookmark(workFolder,
uri("http://msdn.microsoft.com/"),
bs.DEFAULT_INDEX, "");
do_check_eq(bookmarksObserver._itemAddedId, newId3);
do_check_eq(bookmarksObserver._itemAddedParent, workFolder);
do_check_eq(bookmarksObserver._itemAddedIndex, 1);
// change item
bs.setItemTitle(newId3, "MSDN");
do_check_eq(bookmarksObserver._itemChangedProperty, "title");
// remove item
bs.removeItem(newId2);
do_check_eq(bookmarksObserver._itemRemovedId, newId2);
do_check_eq(bookmarksObserver._itemRemovedFolder, workFolder);
do_check_eq(bookmarksObserver._itemRemovedIndex, 0);
// insert item into subfolder
let newId4 = bs.insertBookmark(workFolder,
uri("http://developer.mozilla.org/"),
bs.DEFAULT_INDEX, "");
do_check_eq(bookmarksObserver._itemAddedId, newId4);
do_check_eq(bookmarksObserver._itemAddedParent, workFolder);
do_check_eq(bookmarksObserver._itemAddedIndex, 1);
// create folder
let homeFolder = bs.createFolder(testRoot, "Home", bs.DEFAULT_INDEX);
do_check_eq(bookmarksObserver._itemAddedId, homeFolder);
do_check_eq(bookmarksObserver._itemAddedParent, testRoot);
do_check_eq(bookmarksObserver._itemAddedIndex, 2);
// insert item
let newId5 = bs.insertBookmark(homeFolder, uri("http://espn.com/"),
bs.DEFAULT_INDEX, "");
do_check_eq(bookmarksObserver._itemAddedId, newId5);
do_check_eq(bookmarksObserver._itemAddedParent, homeFolder);
do_check_eq(bookmarksObserver._itemAddedIndex, 0);
// change item
bs.setItemTitle(newId5, "ESPN");
do_check_eq(bookmarksObserver._itemChangedId, newId5);
do_check_eq(bookmarksObserver._itemChangedProperty, "title");
// insert query item
let uri6 = uri("place:domain=google.com&type="+
Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY);
let newId6 = bs.insertBookmark(testRoot, uri6, bs.DEFAULT_INDEX, "");
do_check_eq(bookmarksObserver._itemAddedParent, testRoot);
do_check_eq(bookmarksObserver._itemAddedIndex, 3);
// change item
bs.setItemTitle(newId6, "Google Sites");
do_check_eq(bookmarksObserver._itemChangedProperty, "title");
// test getIdForItemAt
do_check_eq(bs.getIdForItemAt(testRoot, 0), workFolder);
// wrong parent, should return -1
do_check_eq(bs.getIdForItemAt(1337, 0), -1);
// wrong index, should return -1
do_check_eq(bs.getIdForItemAt(testRoot, 1337), -1);
// wrong parent and index, should return -1
do_check_eq(bs.getIdForItemAt(1337, 1337), -1);
// move folder, appending, to different folder
let oldParentCC = getChildCount(testRoot);
bs.moveItem(workFolder, homeFolder, bs.DEFAULT_INDEX);
do_check_eq(bookmarksObserver._itemMovedId, workFolder);
do_check_eq(bookmarksObserver._itemMovedOldParent, testRoot);
do_check_eq(bookmarksObserver._itemMovedOldIndex, 0);
do_check_eq(bookmarksObserver._itemMovedNewParent, homeFolder);
do_check_eq(bookmarksObserver._itemMovedNewIndex, 1);
// test that the new index is properly stored
do_check_eq(bs.getItemIndex(workFolder), 1);
do_check_eq(bs.getFolderIdForItem(workFolder), homeFolder);
// try to get index of the item from within the old parent folder
// check that it has been really removed from there
do_check_neq(bs.getIdForItemAt(testRoot, 0), workFolder);
// check the last item from within the old parent folder
do_check_neq(bs.getIdForItemAt(testRoot, -1), workFolder);
// check the index of the item within the new parent folder
do_check_eq(bs.getIdForItemAt(homeFolder, 1), workFolder);
// try to get index of the last item within the new parent folder
do_check_eq(bs.getIdForItemAt(homeFolder, -1), workFolder);
// XXX expose FolderCount, and check that the old parent has one less child?
do_check_eq(getChildCount(testRoot), oldParentCC-1);
// move item, appending, to different folder
bs.moveItem(newId5, testRoot, bs.DEFAULT_INDEX);
do_check_eq(bookmarksObserver._itemMovedId, newId5);
do_check_eq(bookmarksObserver._itemMovedOldParent, homeFolder);
do_check_eq(bookmarksObserver._itemMovedOldIndex, 0);
do_check_eq(bookmarksObserver._itemMovedNewParent, testRoot);
do_check_eq(bookmarksObserver._itemMovedNewIndex, 3);
// test get folder's index
let tmpFolder = bs.createFolder(testRoot, "tmp", 2);
do_check_eq(bs.getItemIndex(tmpFolder), 2);
// test setKeywordForBookmark
let kwTestItemId = bs.insertBookmark(testRoot, uri("http://keywordtest.com"),
bs.DEFAULT_INDEX, "");
bs.setKeywordForBookmark(kwTestItemId, "bar");
// test getKeywordForBookmark
let k = bs.getKeywordForBookmark(kwTestItemId);
do_check_eq("bar", k);
// test getURIForKeyword
let u = bs.getURIForKeyword("bar");
do_check_eq("http://keywordtest.com/", u.spec);
// test removeFolderChildren
// 1) add/remove each child type (bookmark, separator, folder)
tmpFolder = bs.createFolder(testRoot, "removeFolderChildren",
bs.DEFAULT_INDEX);
bs.insertBookmark(tmpFolder, uri("http://foo9.com/"), bs.DEFAULT_INDEX, "");
bs.createFolder(tmpFolder, "subfolder", bs.DEFAULT_INDEX);
bs.insertSeparator(tmpFolder, bs.DEFAULT_INDEX);
// 2) confirm that folder has 3 children
let options = hs.getNewQueryOptions();
let query = hs.getNewQuery();
query.setFolders([tmpFolder], 1);
try {
let result = hs.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
do_check_eq(rootNode.childCount, 3);
rootNode.containerOpen = false;
} catch (ex) {
do_throw("test removeFolderChildren() - querying for children failed: " + ex);
}
// 3) remove all children
bs.removeFolderChildren(tmpFolder);
// 4) confirm that folder has 0 children
try {
result = hs.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
do_check_eq(rootNode.childCount, 0);
rootNode.containerOpen = false;
} catch (ex) {
do_throw("removeFolderChildren(): " + ex);
}
// XXX - test folderReadOnly
// test bookmark id in query output
try {
options = hs.getNewQueryOptions();
query = hs.getNewQuery();
query.setFolders([testRoot], 1);
let result = hs.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
let cc = rootNode.childCount;
do_print("bookmark itemId test: CC = " + cc);
do_check_true(cc > 0);
for (let i=0; i < cc; ++i) {
let node = rootNode.getChild(i);
if (node.type == node.RESULT_TYPE_FOLDER ||
node.type == node.RESULT_TYPE_URI ||
node.type == node.RESULT_TYPE_SEPARATOR ||
node.type == node.RESULT_TYPE_QUERY) {
do_check_true(node.itemId > 0);
}
else {
do_check_eq(node.itemId, -1);
}
}
rootNode.containerOpen = false;
}
catch (ex) {
do_throw("bookmarks query: " + ex);
}
// test that multiple bookmarks with same URI show up right in bookmark
// folder queries, todo: also to do for complex folder queries
try {
// test uri
let mURI = uri("http://multiple.uris.in.query");
let testFolder = bs.createFolder(testRoot, "test Folder", bs.DEFAULT_INDEX);
// add 2 bookmarks
bs.insertBookmark(testFolder, mURI, bs.DEFAULT_INDEX, "title 1");
bs.insertBookmark(testFolder, mURI, bs.DEFAULT_INDEX, "title 2");
// query
options = hs.getNewQueryOptions();
query = hs.getNewQuery();
query.setFolders([testFolder], 1);
let result = hs.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
let cc = rootNode.childCount;
do_check_eq(cc, 2);
do_check_eq(rootNode.getChild(0).title, "title 1");
do_check_eq(rootNode.getChild(1).title, "title 2");
rootNode.containerOpen = false;
}
catch (ex) {
do_throw("bookmarks query: " + ex);
}
// test change bookmark uri
let newId10 = bs.insertBookmark(testRoot, uri("http://foo10.com/"),
bs.DEFAULT_INDEX, "");
dateAdded = bs.getItemDateAdded(newId10);
// after just inserting, modified should not be set
lastModified = bs.getItemLastModified(newId10);
do_check_eq(lastModified, dateAdded);
// Workaround possible VM timers issues moving lastModified and dateAdded
// to the past.
lastModified -= 1000;
bs.setItemLastModified(newId10, lastModified);
dateAdded -= 1000;
bs.setItemDateAdded(newId10, dateAdded);
bs.changeBookmarkURI(newId10, uri("http://foo11.com/"));
// check that lastModified is set after we change the bookmark uri
lastModified2 = bs.getItemLastModified(newId10);
do_print("test changeBookmarkURI");
do_print("dateAdded = " + dateAdded);
do_print("lastModified = " + lastModified);
do_print("lastModified2 = " + lastModified2);
do_check_true(is_time_ordered(lastModified, lastModified2));
do_check_true(is_time_ordered(dateAdded, lastModified2));
do_check_eq(bookmarksObserver._itemChangedId, newId10);
do_check_eq(bookmarksObserver._itemChangedProperty, "uri");
do_check_eq(bookmarksObserver._itemChangedValue, "http://foo11.com/");
do_check_eq(bookmarksObserver._itemChangedOldValue, "http://foo10.com/");
// test getBookmarkURI
let newId11 = bs.insertBookmark(testRoot, uri("http://foo11.com/"),
bs.DEFAULT_INDEX, "");
let bmURI = bs.getBookmarkURI(newId11);
do_check_eq("http://foo11.com/", bmURI.spec);
// test getBookmarkURI with non-bookmark items
try {
bs.getBookmarkURI(testRoot);
do_throw("getBookmarkURI() should throw for non-bookmark items!");
} catch (ex) {}
// test getItemIndex
let newId12 = bs.insertBookmark(testRoot, uri("http://foo11.com/"), 1, "");
let bmIndex = bs.getItemIndex(newId12);
do_check_eq(1, bmIndex);
// insert a bookmark with title ZZZXXXYYY and then search for it.
// this test confirms that we can find bookmarks that we haven't visited
// (which are "hidden") and that we can find by title.
// see bug #369887 for more details
let newId13 = bs.insertBookmark(testRoot, uri("http://foobarcheese.com/"),
bs.DEFAULT_INDEX, "");
do_check_eq(bookmarksObserver._itemAddedId, newId13);
do_check_eq(bookmarksObserver._itemAddedParent, testRoot);
do_check_eq(bookmarksObserver._itemAddedIndex, 11);
// set bookmark title
bs.setItemTitle(newId13, "ZZZXXXYYY");
do_check_eq(bookmarksObserver._itemChangedId, newId13);
do_check_eq(bookmarksObserver._itemChangedProperty, "title");
do_check_eq(bookmarksObserver._itemChangedValue, "ZZZXXXYYY");
// check if setting an item annotation triggers onItemChanged
bookmarksObserver._itemChangedId = -1;
anno.setItemAnnotation(newId3, "test-annotation", "foo", 0, 0);
do_check_eq(bookmarksObserver._itemChangedId, newId3);
do_check_eq(bookmarksObserver._itemChangedProperty, "test-annotation");
do_check_true(bookmarksObserver._itemChanged_isAnnotationProperty);
do_check_eq(bookmarksObserver._itemChangedValue, "");
// test search on bookmark title ZZZXXXYYY
try {
options = hs.getNewQueryOptions();
options.excludeQueries = 1;
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
query = hs.getNewQuery();
query.searchTerms = "ZZZXXXYYY";
let result = hs.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
let cc = rootNode.childCount;
do_check_eq(cc, 1);
let node = rootNode.getChild(0);
do_check_eq(node.title, "ZZZXXXYYY");
do_check_true(node.itemId > 0);
rootNode.containerOpen = false;
}
catch (ex) {
do_throw("bookmarks query: " + ex);
}
// test dateAdded and lastModified properties
// for a search query
try {
options = hs.getNewQueryOptions();
options.excludeQueries = 1;
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
query = hs.getNewQuery();
query.searchTerms = "ZZZXXXYYY";
let result = hs.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
let cc = rootNode.childCount;
do_check_eq(cc, 1);
let node = rootNode.getChild(0);
do_check_eq(typeof node.dateAdded, "number");
do_check_true(node.dateAdded > 0);
do_check_eq(typeof node.lastModified, "number");
do_check_true(node.lastModified > 0);
rootNode.containerOpen = false;
}
catch (ex) {
do_throw("bookmarks query: " + ex);
}
// test dateAdded and lastModified properties
// for a folder query
try {
options = hs.getNewQueryOptions();
query = hs.getNewQuery();
query.setFolders([testRoot], 1);
let result = hs.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
let cc = rootNode.childCount;
do_check_true(cc > 0);
for (let i = 0; i < cc; i++) {
let node = rootNode.getChild(i);
if (node.type == node.RESULT_TYPE_URI) {
do_check_eq(typeof node.dateAdded, "number");
do_check_true(node.dateAdded > 0);
do_check_eq(typeof node.lastModified, "number");
do_check_true(node.lastModified > 0);
break;
}
}
rootNode.containerOpen = false;
}
catch (ex) {
do_throw("bookmarks query: " + ex);
}
// check setItemLastModified() and setItemDateAdded()
let newId14 = bs.insertBookmark(testRoot, uri("http://bar.tld/"),
bs.DEFAULT_INDEX, "");
dateAdded = bs.getItemDateAdded(newId14);
lastModified = bs.getItemLastModified(newId14);
do_check_eq(lastModified, dateAdded);
bs.setItemLastModified(newId14, 1234000000000000);
let fakeLastModified = bs.getItemLastModified(newId14);
do_check_eq(fakeLastModified, 1234000000000000);
bs.setItemDateAdded(newId14, 4321000000000000);
let fakeDateAdded = bs.getItemDateAdded(newId14);
do_check_eq(fakeDateAdded, 4321000000000000);
// ensure that removing an item removes its annotations
do_check_true(anno.itemHasAnnotation(newId3, "test-annotation"));
bs.removeItem(newId3);
do_check_false(anno.itemHasAnnotation(newId3, "test-annotation"));
// bug 378820
let uri1 = uri("http://foo.tld/a");
bs.insertBookmark(testRoot, uri1, bs.DEFAULT_INDEX, "");
yield PlacesTestUtils.addVisits(uri1);
// bug 646993 - test bookmark titles longer than the maximum allowed length
let title15 = Array(TITLE_LENGTH_MAX + 5).join("X");
let title15expected = title15.substring(0, TITLE_LENGTH_MAX);
let newId15 = bs.insertBookmark(testRoot, uri("http://evil.com/"),
bs.DEFAULT_INDEX, title15);
do_check_eq(bs.getItemTitle(newId15).length,
title15expected.length);
do_check_eq(bookmarksObserver._itemAddedTitle, title15expected);
// test title length after updates
bs.setItemTitle(newId15, title15 + " updated");
do_check_eq(bs.getItemTitle(newId15).length,
title15expected.length);
do_check_eq(bookmarksObserver._itemChangedId, newId15);
do_check_eq(bookmarksObserver._itemChangedProperty, "title");
do_check_eq(bookmarksObserver._itemChangedValue, title15expected);
testSimpleFolderResult();
});
function testSimpleFolderResult() {
// the time before we create a folder, in microseconds
// Workaround possible VM timers issues subtracting 1us.
let beforeCreate = Date.now() * 1000 - 1;
do_check_true(beforeCreate > 0);
// create a folder
let parent = bs.createFolder(root, "test", bs.DEFAULT_INDEX);
let dateCreated = bs.getItemDateAdded(parent);
do_print("check that the folder was created with a valid dateAdded");
do_print("beforeCreate = " + beforeCreate);
do_print("dateCreated = " + dateCreated);
do_check_true(is_time_ordered(beforeCreate, dateCreated));
// the time before we insert, in microseconds
// Workaround possible VM timers issues subtracting 1ms.
let beforeInsert = Date.now() * 1000 - 1;
do_check_true(beforeInsert > 0);
// insert a separator
let sep = bs.insertSeparator(parent, bs.DEFAULT_INDEX);
let dateAdded = bs.getItemDateAdded(sep);
do_print("check that the separator was created with a valid dateAdded");
do_print("beforeInsert = " + beforeInsert);
do_print("dateAdded = " + dateAdded);
do_check_true(is_time_ordered(beforeInsert, dateAdded));
// re-set item title separately so can test nodes' last modified
let item = bs.insertBookmark(parent, uri("about:blank"),
bs.DEFAULT_INDEX, "");
bs.setItemTitle(item, "test bookmark");
// see above
let folder = bs.createFolder(parent, "test folder", bs.DEFAULT_INDEX);
bs.setItemTitle(folder, "test folder");
let longName = Array(TITLE_LENGTH_MAX + 5).join("A");
let folderLongName = bs.createFolder(parent, longName, bs.DEFAULT_INDEX);
do_check_eq(bookmarksObserver._itemAddedTitle, longName.substring(0, TITLE_LENGTH_MAX));
let options = hs.getNewQueryOptions();
let query = hs.getNewQuery();
query.setFolders([parent], 1);
let result = hs.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
do_check_eq(rootNode.childCount, 4);
let node = rootNode.getChild(0);
do_check_true(node.dateAdded > 0);
do_check_eq(node.lastModified, node.dateAdded);
do_check_eq(node.itemId, sep);
do_check_eq(node.title, "");
node = rootNode.getChild(1);
do_check_eq(node.itemId, item);
do_check_true(node.dateAdded > 0);
do_check_true(node.lastModified > 0);
do_check_eq(node.title, "test bookmark");
node = rootNode.getChild(2);
do_check_eq(node.itemId, folder);
do_check_eq(node.title, "test folder");
do_check_true(node.dateAdded > 0);
do_check_true(node.lastModified > 0);
node = rootNode.getChild(3);
do_check_eq(node.itemId, folderLongName);
do_check_eq(node.title, longName.substring(0, TITLE_LENGTH_MAX));
do_check_true(node.dateAdded > 0);
do_check_true(node.lastModified > 0);
// update with another long title
bs.setItemTitle(folderLongName, longName + " updated");
do_check_eq(bookmarksObserver._itemChangedId, folderLongName);
do_check_eq(bookmarksObserver._itemChangedProperty, "title");
do_check_eq(bookmarksObserver._itemChangedValue, longName.substring(0, TITLE_LENGTH_MAX));
node = rootNode.getChild(3);
do_check_eq(node.title, longName.substring(0, TITLE_LENGTH_MAX));
rootNode.containerOpen = false;
}
function getChildCount(aFolderId) {
let cc = -1;
try {
let options = hs.getNewQueryOptions();
let query = hs.getNewQuery();
query.setFolders([aFolderId], 1);
let result = hs.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
cc = rootNode.childCount;
rootNode.containerOpen = false;
} catch (ex) {
do_throw("getChildCount failed: " + ex);
}
return cc;
}

View file

@ -0,0 +1,116 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function* test_eraseEverything() {
yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://example.com/") });
yield PlacesTestUtils.addVisits({ uri: NetUtil.newURI("http://mozilla.org/") });
let frecencyForExample = frecencyForUrl("http://example.com/");
let frecencyForMozilla = frecencyForUrl("http://example.com/");
Assert.ok(frecencyForExample > 0);
Assert.ok(frecencyForMozilla > 0);
let unfiledFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(unfiledFolder);
let unfiledBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/" });
checkBookmarkObject(unfiledBookmark);
let unfiledBookmarkInFolder =
yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledFolder.guid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://mozilla.org/" });
checkBookmarkObject(unfiledBookmarkInFolder);
PlacesUtils.annotations.setItemAnnotation((yield PlacesUtils.promiseItemId(unfiledBookmarkInFolder.guid)),
"testanno1", "testvalue1", 0, 0);
let menuFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(menuFolder);
let menuBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/" });
checkBookmarkObject(menuBookmark);
let menuBookmarkInFolder =
yield PlacesUtils.bookmarks.insert({ parentGuid: menuFolder.guid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://mozilla.org/" });
checkBookmarkObject(menuBookmarkInFolder);
PlacesUtils.annotations.setItemAnnotation((yield PlacesUtils.promiseItemId(menuBookmarkInFolder.guid)),
"testanno1", "testvalue1", 0, 0);
let toolbarFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(toolbarFolder);
let toolbarBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/" });
checkBookmarkObject(toolbarBookmark);
let toolbarBookmarkInFolder =
yield PlacesUtils.bookmarks.insert({ parentGuid: toolbarFolder.guid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://mozilla.org/" });
checkBookmarkObject(toolbarBookmarkInFolder);
PlacesUtils.annotations.setItemAnnotation((yield PlacesUtils.promiseItemId(toolbarBookmarkInFolder.guid)),
"testanno1", "testvalue1", 0, 0);
yield PlacesTestUtils.promiseAsyncUpdates();
Assert.ok(frecencyForUrl("http://example.com/") > frecencyForExample);
Assert.ok(frecencyForUrl("http://example.com/") > frecencyForMozilla);
yield PlacesUtils.bookmarks.eraseEverything();
Assert.equal(frecencyForUrl("http://example.com/"), frecencyForExample);
Assert.equal(frecencyForUrl("http://example.com/"), frecencyForMozilla);
// Check there are no orphan annotations.
let conn = yield PlacesUtils.promiseDBConnection();
let annoAttrs = yield conn.execute(`SELECT id, name FROM moz_anno_attributes`);
// Bug 1306445 will eventually remove the mobile root anno.
Assert.equal(annoAttrs.length, 1);
Assert.equal(annoAttrs[0].getResultByName("name"), PlacesUtils.MOBILE_ROOT_ANNO);
let annos = rows = yield conn.execute(`SELECT item_id, anno_attribute_id FROM moz_items_annos`);
Assert.equal(annos.length, 1);
Assert.equal(annos[0].getResultByName("item_id"), PlacesUtils.mobileFolderId);
Assert.equal(annos[0].getResultByName("anno_attribute_id"), annoAttrs[0].getResultByName("id"));
});
add_task(function* test_eraseEverything_roots() {
yield PlacesUtils.bookmarks.eraseEverything();
// Ensure the roots have not been removed.
Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.unfiledGuid));
Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.toolbarGuid));
Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.menuGuid));
Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.tagsGuid));
Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.rootGuid));
});
add_task(function* test_eraseEverything_reparented() {
// Create a folder with 1 bookmark in it...
let folder1 = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER
});
let bookmark1 = yield PlacesUtils.bookmarks.insert({
parentGuid: folder1.guid,
url: "http://example.com/"
});
// ...and a second folder.
let folder2 = yield PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER
});
// Reparent the bookmark to the 2nd folder.
bookmark1.parentGuid = folder2.guid;
yield PlacesUtils.bookmarks.update(bookmark1);
// Erase everything.
yield PlacesUtils.bookmarks.eraseEverything();
// All the above items should no longer be in the GUIDHelper cache.
for (let guid of [folder1.guid, bookmark1.guid, folder2.guid]) {
yield Assert.rejects(PlacesUtils.promiseItemId(guid),
/no item found for the given GUID/);
}
});

View file

@ -0,0 +1,310 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
var gAccumulator = {
get callback() {
this.results = [];
return result => this.results.push(result);
}
};
add_task(function* invalid_input_throws() {
Assert.throws(() => PlacesUtils.bookmarks.fetch(),
/Input should be a valid object/);
Assert.throws(() => PlacesUtils.bookmarks.fetch(null),
/Input should be a valid object/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: "123456789012",
parentGuid: "012345678901" }),
/The following properties were expected: index/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: "123456789012",
index: 0 }),
/The following properties were expected: parentGuid/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({}),
/Unexpected number of conditions provided: 0/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: "123456789012",
parentGuid: "012345678901",
index: 0 }),
/Unexpected number of conditions provided: 2/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: "123456789012",
url: "http://example.com"}),
/Unexpected number of conditions provided: 2/);
Assert.throws(() => PlacesUtils.bookmarks.fetch("test"),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch(123),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: "test" }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: null }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ guid: 123 }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: "test",
index: 0 }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: null,
index: 0 }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: 123,
index: 0 }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: "123456789012",
index: "0" }),
/Invalid value for property 'index'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: "123456789012",
index: null }),
/Invalid value for property 'index'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ parentGuid: "123456789012",
index: -10 }),
/Invalid value for property 'index'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ url: "http://te st/" }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ url: null }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch({ url: -10 }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.fetch("123456789012", "test"),
/onResult callback must be a valid function/);
Assert.throws(() => PlacesUtils.bookmarks.fetch("123456789012", {}),
/onResult callback must be a valid function/);
});
add_task(function* fetch_nonexistent_guid() {
let bm = yield PlacesUtils.bookmarks.fetch({ guid: "123456789012" },
gAccumulator.callback);
Assert.equal(bm, null);
Assert.equal(gAccumulator.results.length, 0);
});
add_task(function* fetch_bookmark() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark" });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.fetch(bm1.guid,
gAccumulator.callback);
checkBookmarkObject(bm2);
Assert.equal(gAccumulator.results.length, 1);
checkBookmarkObject(gAccumulator.results[0]);
Assert.deepEqual(gAccumulator.results[0], bm1);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
Assert.equal(bm2.url.href, "http://example.com/");
Assert.equal(bm2.title, "a bookmark");
yield PlacesUtils.bookmarks.remove(bm1.guid);
});
add_task(function* fetch_bookmar_empty_title() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "" });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.index, 0);
Assert.ok(!("title" in bm2));
yield PlacesUtils.bookmarks.remove(bm1.guid);
});
add_task(function* fetch_folder() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_FOLDER);
Assert.equal(bm2.title, "a folder");
Assert.ok(!("url" in bm2));
yield PlacesUtils.bookmarks.remove(bm1.guid);
});
add_task(function* fetch_folder_empty_title() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "" });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.index, 0);
Assert.ok(!("title" in bm2));
yield PlacesUtils.bookmarks.remove(bm1.guid);
});
add_task(function* fetch_separator() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
Assert.ok(!("url" in bm2));
Assert.ok(!("title" in bm2));
yield PlacesUtils.bookmarks.remove(bm1.guid);
});
add_task(function* fetch_byposition_nonexisting_parentGuid() {
let bm = yield PlacesUtils.bookmarks.fetch({ parentGuid: "123456789012",
index: 0 },
gAccumulator.callback);
Assert.equal(bm, null);
Assert.equal(gAccumulator.results.length, 0);
});
add_task(function* fetch_byposition_nonexisting_index() {
let bm = yield PlacesUtils.bookmarks.fetch({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
index: 100 },
gAccumulator.callback);
Assert.equal(bm, null);
Assert.equal(gAccumulator.results.length, 0);
});
add_task(function* fetch_byposition() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark" });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.fetch({ parentGuid: bm1.parentGuid,
index: bm1.index },
gAccumulator.callback);
checkBookmarkObject(bm2);
Assert.equal(gAccumulator.results.length, 1);
checkBookmarkObject(gAccumulator.results[0]);
Assert.deepEqual(gAccumulator.results[0], bm1);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
Assert.equal(bm2.url.href, "http://example.com/");
Assert.equal(bm2.title, "a bookmark");
});
add_task(function* fetch_byposition_default_index() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/last",
title: "last child" });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.fetch({ parentGuid: bm1.parentGuid,
index: PlacesUtils.bookmarks.DEFAULT_INDEX },
gAccumulator.callback);
checkBookmarkObject(bm2);
Assert.equal(gAccumulator.results.length, 1);
checkBookmarkObject(gAccumulator.results[0]);
Assert.deepEqual(gAccumulator.results[0], bm1);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 1);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
Assert.equal(bm2.url.href, "http://example.com/last");
Assert.equal(bm2.title, "last child");
yield PlacesUtils.bookmarks.remove(bm1.guid);
});
add_task(function* fetch_byurl_nonexisting() {
let bm = yield PlacesUtils.bookmarks.fetch({ url: "http://nonexisting.com/" },
gAccumulator.callback);
Assert.equal(bm, null);
Assert.equal(gAccumulator.results.length, 0);
});
add_task(function* fetch_byurl() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://byurl.com/",
title: "a bookmark" });
checkBookmarkObject(bm1);
// Also ensure that fecth-by-url excludes the tags folder.
PlacesUtils.tagging.tagURI(uri(bm1.url.href), ["Test Tag"]);
let bm2 = yield PlacesUtils.bookmarks.fetch({ url: bm1.url },
gAccumulator.callback);
checkBookmarkObject(bm2);
Assert.equal(gAccumulator.results.length, 1);
checkBookmarkObject(gAccumulator.results[0]);
Assert.deepEqual(gAccumulator.results[0], bm1);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
Assert.equal(bm2.url.href, "http://byurl.com/");
Assert.equal(bm2.title, "a bookmark");
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://byurl.com/",
title: "a bookmark" });
let bm4 = yield PlacesUtils.bookmarks.fetch({ url: bm1.url },
gAccumulator.callback);
checkBookmarkObject(bm4);
Assert.deepEqual(bm3, bm4);
Assert.equal(gAccumulator.results.length, 2);
gAccumulator.results.forEach(checkBookmarkObject);
Assert.deepEqual(gAccumulator.results[0], bm4);
// After an update the returned bookmark should change.
yield PlacesUtils.bookmarks.update({ guid: bm1.guid, title: "new title" });
let bm5 = yield PlacesUtils.bookmarks.fetch({ url: bm1.url },
gAccumulator.callback);
checkBookmarkObject(bm5);
// Cannot use deepEqual cause lastModified changed.
Assert.equal(bm1.guid, bm5.guid);
Assert.ok(bm5.lastModified > bm1.lastModified);
Assert.equal(gAccumulator.results.length, 2);
gAccumulator.results.forEach(checkBookmarkObject);
Assert.deepEqual(gAccumulator.results[0], bm5);
// cleanup
PlacesUtils.tagging.untagURI(uri(bm1.url.href), ["Test Tag"]);
});
function run_test() {
run_next_test();
}

View file

@ -0,0 +1,44 @@
add_task(function* invalid_input_throws() {
Assert.throws(() => PlacesUtils.bookmarks.getRecent(),
/numberOfItems argument is required/);
Assert.throws(() => PlacesUtils.bookmarks.getRecent("abc"),
/numberOfItems argument must be an integer/);
Assert.throws(() => PlacesUtils.bookmarks.getRecent(1.2),
/numberOfItems argument must be an integer/);
Assert.throws(() => PlacesUtils.bookmarks.getRecent(0),
/numberOfItems argument must be greater than zero/);
Assert.throws(() => PlacesUtils.bookmarks.getRecent(-1),
/numberOfItems argument must be greater than zero/);
});
add_task(function* getRecent_returns_recent_bookmarks() {
yield PlacesUtils.bookmarks.eraseEverything();
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/path",
title: "another bookmark" });
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.net/",
title: "another bookmark" });
let bm4 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.net/path",
title: "yet another bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
checkBookmarkObject(bm3);
checkBookmarkObject(bm4);
let results = yield PlacesUtils.bookmarks.getRecent(3);
Assert.equal(results.length, 3);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm4, results[0]);
checkBookmarkObject(results[1]);
Assert.deepEqual(bm3, results[1]);
checkBookmarkObject(results[2]);
Assert.deepEqual(bm2, results[2]);
yield PlacesUtils.bookmarks.eraseEverything();
});

View file

@ -0,0 +1,264 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function* invalid_input_throws() {
Assert.throws(() => PlacesUtils.bookmarks.insert(),
/Input should be a valid object/);
Assert.throws(() => PlacesUtils.bookmarks.insert(null),
/Input should be a valid object/);
Assert.throws(() => PlacesUtils.bookmarks.insert({}),
/The following properties were expected/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ guid: "test" }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ guid: null }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ guid: 123 }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ parentGuid: "test" }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ parentGuid: null }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ parentGuid: 123 }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ index: "1" }),
/Invalid value for property 'index'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ index: -10 }),
/Invalid value for property 'index'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ dateAdded: -10 }),
/Invalid value for property 'dateAdded'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ dateAdded: "today" }),
/Invalid value for property 'dateAdded'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ dateAdded: Date.now() }),
/Invalid value for property 'dateAdded'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ lastModified: -10 }),
/Invalid value for property 'lastModified'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ lastModified: "today" }),
/Invalid value for property 'lastModified'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ lastModified: Date.now() }),
/Invalid value for property 'lastModified'/);
let time = new Date();
let future = new Date(time + 86400000);
Assert.throws(() => PlacesUtils.bookmarks.insert({ dateAdded: future,
lastModified: time }),
/Invalid value for property 'dateAdded'/);
let past = new Date(time - 86400000);
Assert.throws(() => PlacesUtils.bookmarks.insert({ lastModified: past }),
/Invalid value for property 'lastModified'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: -1 }),
/Invalid value for property 'type'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: 100 }),
/Invalid value for property 'type'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: "bookmark" }),
/Invalid value for property 'type'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
title: -1 }),
/Invalid value for property 'title'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: 10 }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://te st" }),
/Invalid value for property 'url'/);
let longurl = "http://www.example.com/";
for (let i = 0; i < 65536; i++) {
longurl += "a";
}
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: longurl }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: NetUtil.newURI(longurl) }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "te st" }),
/Invalid value for property 'url'/);
});
add_task(function* invalid_properties_for_bookmark_type() {
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
url: "http://www.moz.com/" }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
url: "http://www.moz.com/" }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
title: "test" }),
/Invalid value for property 'title'/);
});
add_task(function* long_title_trim() {
let longtitle = "a";
for (let i = 0; i < 4096; i++) {
longtitle += "a";
}
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: longtitle });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.index, 0);
Assert.equal(bm.dateAdded, bm.lastModified);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_FOLDER);
Assert.equal(bm.title.length, 4096, "title should have been trimmed");
Assert.ok(!("url" in bm), "url should not be set");
});
add_task(function* create_separator() {
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
index: PlacesUtils.bookmarks.DEFAULT_INDEX });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.index, 1);
Assert.equal(bm.dateAdded, bm.lastModified);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
Assert.ok(!("title" in bm), "title should not be set");
});
add_task(function* create_separator_w_title_fail() {
try {
yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
title: "a separator" });
Assert.ok(false, "Trying to set title for a separator should reject");
} catch (ex) {}
});
add_task(function* create_separator_invalid_parent_fail() {
try {
yield PlacesUtils.bookmarks.insert({ parentGuid: "123456789012",
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
title: "a separator" });
Assert.ok(false, "Trying to create an item in a non existing parent reject");
} catch (ex) {}
});
add_task(function* create_separator_given_guid() {
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
guid: "123456789012" });
checkBookmarkObject(bm);
Assert.equal(bm.guid, "123456789012");
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.index, 2);
Assert.equal(bm.dateAdded, bm.lastModified);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
Assert.ok(!("title" in bm), "title should not be set");
});
add_task(function* create_item_given_guid_no_type_fail() {
try {
yield PlacesUtils.bookmarks.insert({ parentGuid: "123456789012" });
Assert.ok(false, "Trying to create an item with a given guid but no type should reject");
} catch (ex) {}
});
add_task(function* create_separator_big_index() {
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
index: 9999 });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.index, 3);
Assert.equal(bm.dateAdded, bm.lastModified);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
Assert.ok(!("title" in bm), "title should not be set");
});
add_task(function* create_separator_given_dateAdded() {
let time = new Date();
let past = new Date(time - 86400000);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
dateAdded: past });
checkBookmarkObject(bm);
Assert.equal(bm.dateAdded, past);
Assert.equal(bm.lastModified, past);
});
add_task(function* create_folder() {
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.dateAdded, bm.lastModified);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_FOLDER);
Assert.ok(!("title" in bm), "title should not be set");
// And then create a nested folder.
let parentGuid = bm.guid;
bm = yield PlacesUtils.bookmarks.insert({ parentGuid: parentGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, parentGuid);
Assert.equal(bm.index, 0);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_FOLDER);
Assert.strictEqual(bm.title, "a folder");
});
add_task(function* create_bookmark() {
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
let parentGuid = bm.guid;
bm = yield PlacesUtils.bookmarks.insert({ parentGuid: parentGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark" });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, parentGuid);
Assert.equal(bm.index, 0);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
Assert.equal(bm.url.href, "http://example.com/");
Assert.equal(bm.title, "a bookmark");
// Check parent lastModified.
let parent = yield PlacesUtils.bookmarks.fetch({ guid: bm.parentGuid });
Assert.deepEqual(parent.lastModified, bm.dateAdded);
bm = yield PlacesUtils.bookmarks.insert({ parentGuid: parentGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: new URL("http://example.com/") });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, parentGuid);
Assert.equal(bm.index, 1);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
Assert.equal(bm.url.href, "http://example.com/");
Assert.ok(!("title" in bm), "title should not be set");
});
add_task(function* create_bookmark_frecency() {
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark" });
checkBookmarkObject(bm);
yield PlacesTestUtils.promiseAsyncUpdates();
Assert.ok(frecencyForUrl(bm.url) > 0, "Check frecency has been updated")
});
add_task(function* create_bookmark_without_type() {
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
Assert.equal(bm.url.href, "http://example.com/");
Assert.equal(bm.title, "a bookmark");
});
function run_test() {
run_next_test();
}

View file

@ -0,0 +1,527 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function* insert_separator_notification() {
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
parentGuid: PlacesUtils.bookmarks.unfiledGuid});
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
observer.check([ { name: "onItemAdded",
arguments: [ itemId, parentId, bm.index, bm.type,
null, null, bm.dateAdded,
bm.guid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* insert_folder_notification() {
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
title: "a folder" });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
observer.check([ { name: "onItemAdded",
arguments: [ itemId, parentId, bm.index, bm.type,
null, bm.title, bm.dateAdded,
bm.guid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* insert_folder_notitle_notification() {
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
observer.check([ { name: "onItemAdded",
arguments: [ itemId, parentId, bm.index, bm.type,
null, null, bm.dateAdded,
bm.guid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* insert_bookmark_notification() {
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://example.com/"),
title: "a bookmark" });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
observer.check([ { name: "onItemAdded",
arguments: [ itemId, parentId, bm.index, bm.type,
bm.url, bm.title, bm.dateAdded,
bm.guid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* insert_bookmark_notitle_notification() {
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://example.com/") });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
observer.check([ { name: "onItemAdded",
arguments: [ itemId, parentId, bm.index, bm.type,
bm.url, null, bm.dateAdded,
bm.guid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* insert_bookmark_tag_notification() {
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://tag.example.com/") });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
let tagFolder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.tagsGuid,
title: "tag" });
let observer = expectNotifications();
let tag = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: tagFolder.guid,
url: new URL("http://tag.example.com/") });
let tagId = yield PlacesUtils.promiseItemId(tag.guid);
let tagParentId = yield PlacesUtils.promiseItemId(tag.parentGuid);
observer.check([ { name: "onItemAdded",
arguments: [ tagId, tagParentId, tag.index, tag.type,
tag.url, null, tag.dateAdded,
tag.guid, tag.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemChanged",
arguments: [ itemId, "tags", false, "",
bm.lastModified, bm.type, parentId,
bm.guid, bm.parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* update_bookmark_lastModified() {
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://lastmod.example.com/") });
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
lastModified: new Date() });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
observer.check([ { name: "onItemChanged",
arguments: [ itemId, "lastModified", false,
`${bm.lastModified * 1000}`, bm.lastModified,
bm.type, parentId, bm.guid, bm.parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* update_bookmark_title() {
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://title.example.com/") });
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
title: "new title" });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
observer.check([ { name: "onItemChanged",
arguments: [ itemId, "title", false, bm.title,
bm.lastModified, bm.type, parentId, bm.guid,
bm.parentGuid, "", Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* update_bookmark_uri() {
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://url.example.com/") });
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
url: "http://mozilla.org/" });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
observer.check([ { name: "onItemChanged",
arguments: [ itemId, "uri", false, bm.url.href,
bm.lastModified, bm.type, parentId, bm.guid,
bm.parentGuid, "http://url.example.com/",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* update_move_same_folder() {
// Ensure there are at least two items in place (others test do so for us,
// but we don't have to depend on that).
yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://move.example.com/") });
let bmItemId = yield PlacesUtils.promiseItemId(bm.guid);
let bmParentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
let bmOldIndex = bm.index;
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
index: 0 });
Assert.equal(bm.index, 0);
observer.check([ { name: "onItemMoved",
arguments: [ bmItemId, bmParentId, bmOldIndex, bmParentId, bm.index,
bm.type, bm.guid, bm.parentGuid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
// Test that we get the right index for DEFAULT_INDEX input.
bmOldIndex = 0;
observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
index: PlacesUtils.bookmarks.DEFAULT_INDEX });
Assert.ok(bm.index > 0);
observer.check([ { name: "onItemMoved",
arguments: [ bmItemId, bmParentId, bmOldIndex, bmParentId, bm.index,
bm.type, bm.guid, bm.parentGuid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* update_move_different_folder() {
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://move.example.com/") });
let folder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let bmItemId = yield PlacesUtils.promiseItemId(bm.guid);
let bmOldParentId = PlacesUtils.unfiledBookmarksFolderId;
let bmOldIndex = bm.index;
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
parentGuid: folder.guid,
index: PlacesUtils.bookmarks.DEFAULT_INDEX });
Assert.equal(bm.index, 0);
let bmNewParentId = yield PlacesUtils.promiseItemId(folder.guid);
observer.check([ { name: "onItemMoved",
arguments: [ bmItemId, bmOldParentId, bmOldIndex, bmNewParentId,
bm.index, bm.type, bm.guid,
PlacesUtils.bookmarks.unfiledGuid,
bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* remove_bookmark() {
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://remove.example.com/") });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.remove(bm.guid);
// TODO (Bug 653910): onItemAnnotationRemoved notified even if there were no
// annotations.
observer.check([ { name: "onItemRemoved",
arguments: [ itemId, parentId, bm.index, bm.type, bm.url,
bm.guid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* remove_folder() {
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.remove(bm.guid);
observer.check([ { name: "onItemRemoved",
arguments: [ itemId, parentId, bm.index, bm.type, null,
bm.guid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* remove_bookmark_tag_notification() {
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://untag.example.com/") });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
let tagFolder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.tagsGuid,
title: "tag" });
let tag = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: tagFolder.guid,
url: new URL("http://untag.example.com/") });
let tagId = yield PlacesUtils.promiseItemId(tag.guid);
let tagParentId = yield PlacesUtils.promiseItemId(tag.parentGuid);
let observer = expectNotifications();
yield PlacesUtils.bookmarks.remove(tag.guid);
observer.check([ { name: "onItemRemoved",
arguments: [ tagId, tagParentId, tag.index, tag.type,
tag.url, tag.guid, tag.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemChanged",
arguments: [ itemId, "tags", false, "",
bm.lastModified, bm.type, parentId,
bm.guid, bm.parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* remove_folder_notification() {
let folder1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let folder1Id = yield PlacesUtils.promiseItemId(folder1.guid);
let folder1ParentId = yield PlacesUtils.promiseItemId(folder1.parentGuid);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: folder1.guid,
url: new URL("http://example.com/") });
let bmItemId = yield PlacesUtils.promiseItemId(bm.guid);
let folder2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: folder1.guid });
let folder2Id = yield PlacesUtils.promiseItemId(folder2.guid);
let bm2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: folder2.guid,
url: new URL("http://example.com/") });
let bm2ItemId = yield PlacesUtils.promiseItemId(bm2.guid);
let observer = expectNotifications();
yield PlacesUtils.bookmarks.remove(folder1.guid);
observer.check([ { name: "onItemRemoved",
arguments: [ bm2ItemId, folder2Id, bm2.index, bm2.type,
bm2.url, bm2.guid, bm2.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemRemoved",
arguments: [ folder2Id, folder1Id, folder2.index,
folder2.type, null, folder2.guid,
folder2.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemRemoved",
arguments: [ bmItemId, folder1Id, bm.index, bm.type,
bm.url, bm.guid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemRemoved",
arguments: [ folder1Id, folder1ParentId, folder1.index,
folder1.type, null, folder1.guid,
folder1.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
});
add_task(function* eraseEverything_notification() {
// Let's start from a clean situation.
yield PlacesUtils.bookmarks.eraseEverything();
let folder1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let folder1Id = yield PlacesUtils.promiseItemId(folder1.guid);
let folder1ParentId = yield PlacesUtils.promiseItemId(folder1.parentGuid);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: folder1.guid,
url: new URL("http://example.com/") });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
let folder2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let folder2Id = yield PlacesUtils.promiseItemId(folder2.guid);
let folder2ParentId = yield PlacesUtils.promiseItemId(folder2.parentGuid);
let toolbarBm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
url: new URL("http://example.com/") });
let toolbarBmId = yield PlacesUtils.promiseItemId(toolbarBm.guid);
let toolbarBmParentId = yield PlacesUtils.promiseItemId(toolbarBm.parentGuid);
let menuBm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.menuGuid,
url: new URL("http://example.com/") });
let menuBmId = yield PlacesUtils.promiseItemId(menuBm.guid);
let menuBmParentId = yield PlacesUtils.promiseItemId(menuBm.parentGuid);
let observer = expectNotifications();
yield PlacesUtils.bookmarks.eraseEverything();
// Bookmarks should always be notified before their parents.
observer.check([ { name: "onItemRemoved",
arguments: [ itemId, parentId, bm.index, bm.type,
bm.url, bm.guid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemRemoved",
arguments: [ folder2Id, folder2ParentId, folder2.index,
folder2.type, null, folder2.guid,
folder2.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemRemoved",
arguments: [ folder1Id, folder1ParentId, folder1.index,
folder1.type, null, folder1.guid,
folder1.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemRemoved",
arguments: [ menuBmId, menuBmParentId,
menuBm.index, menuBm.type,
menuBm.url, menuBm.guid,
menuBm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemRemoved",
arguments: [ toolbarBmId, toolbarBmParentId,
toolbarBm.index, toolbarBm.type,
toolbarBm.url, toolbarBm.guid,
toolbarBm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
]);
});
add_task(function* eraseEverything_reparented_notification() {
// Let's start from a clean situation.
yield PlacesUtils.bookmarks.eraseEverything();
let folder1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let folder1Id = yield PlacesUtils.promiseItemId(folder1.guid);
let folder1ParentId = yield PlacesUtils.promiseItemId(folder1.parentGuid);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: folder1.guid,
url: new URL("http://example.com/") });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let folder2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let folder2Id = yield PlacesUtils.promiseItemId(folder2.guid);
let folder2ParentId = yield PlacesUtils.promiseItemId(folder2.parentGuid);
bm.parentGuid = folder2.guid;
bm = yield PlacesUtils.bookmarks.update(bm);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
let observer = expectNotifications();
yield PlacesUtils.bookmarks.eraseEverything();
// Bookmarks should always be notified before their parents.
observer.check([ { name: "onItemRemoved",
arguments: [ itemId, parentId, bm.index, bm.type,
bm.url, bm.guid, bm.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemRemoved",
arguments: [ folder2Id, folder2ParentId, folder2.index,
folder2.type, null, folder2.guid,
folder2.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemRemoved",
arguments: [ folder1Id, folder1ParentId, folder1.index,
folder1.type, null, folder1.guid,
folder1.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
]);
});
add_task(function* reorder_notification() {
let bookmarks = [
{ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example1.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid
},
{ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid
},
{ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
parentGuid: PlacesUtils.bookmarks.unfiledGuid
},
{ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example2.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid
},
{ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example3.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid
},
];
let sorted = [];
for (let bm of bookmarks) {
sorted.push(yield PlacesUtils.bookmarks.insert(bm));
}
// Randomly reorder the array.
sorted.sort(() => 0.5 - Math.random());
let observer = expectNotifications();
yield PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.unfiledGuid,
sorted.map(bm => bm.guid));
let expectedNotifications = [];
for (let i = 0; i < sorted.length; ++i) {
let child = sorted[i];
let childId = yield PlacesUtils.promiseItemId(child.guid);
expectedNotifications.push({ name: "onItemMoved",
arguments: [ childId,
PlacesUtils.unfiledBookmarksFolderId,
child.index,
PlacesUtils.unfiledBookmarksFolderId,
i,
child.type,
child.guid,
child.parentGuid,
child.parentGuid,
Ci.nsINavBookmarksService.SOURCE_DEFAULT
] });
}
observer.check(expectedNotifications);
});
function expectNotifications() {
let notifications = [];
let observer = new Proxy(NavBookmarkObserver, {
get(target, name) {
if (name == "check") {
PlacesUtils.bookmarks.removeObserver(observer);
return expectedNotifications =>
Assert.deepEqual(notifications, expectedNotifications);
}
if (name.startsWith("onItem")) {
return (...origArgs) => {
let args = Array.from(origArgs, arg => {
if (arg && arg instanceof Ci.nsIURI)
return new URL(arg.spec);
if (arg && typeof(arg) == "number" && arg >= Date.now() * 1000)
return new Date(parseInt(arg/1000));
return arg;
});
notifications.push({ name: name, arguments: args });
}
}
if (name in target)
return target[name];
return undefined;
}
});
PlacesUtils.bookmarks.addObserver(observer, false);
return observer;
}
function run_test() {
run_next_test();
}

View file

@ -0,0 +1,204 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function* invalid_input_throws() {
Assert.throws(() => PlacesUtils.bookmarks.remove(),
/Input should be a valid object/);
Assert.throws(() => PlacesUtils.bookmarks.remove(null),
/Input should be a valid object/);
Assert.throws(() => PlacesUtils.bookmarks.remove("test"),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.remove(123),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.remove({ guid: "test" }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.remove({ guid: null }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.remove({ guid: 123 }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.remove({ parentGuid: "test" }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.remove({ parentGuid: null }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.remove({ parentGuid: 123 }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.remove({ url: "http://te st/" }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.remove({ url: null }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.remove({ url: -10 }),
/Invalid value for property 'url'/);
});
add_task(function* remove_nonexistent_guid() {
try {
yield PlacesUtils.bookmarks.remove({ guid: "123456789012"});
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/No bookmarks found for the provided GUID/.test(ex));
}
});
add_task(function* remove_roots_fail() {
let guids = [PlacesUtils.bookmarks.rootGuid,
PlacesUtils.bookmarks.unfiledGuid,
PlacesUtils.bookmarks.menuGuid,
PlacesUtils.bookmarks.toolbarGuid,
PlacesUtils.bookmarks.tagsGuid,
PlacesUtils.bookmarks.mobileGuid];
for (let guid of guids) {
Assert.throws(() => PlacesUtils.bookmarks.remove(guid),
/It's not possible to remove Places root folders/);
}
});
add_task(function* remove_normal_folder_under_root_succeeds() {
let folder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.rootGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(folder);
let removed_folder = yield PlacesUtils.bookmarks.remove(folder);
Assert.deepEqual(folder, removed_folder);
Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(folder.guid)), null);
});
add_task(function* remove_bookmark() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark" });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
Assert.equal(bm2.url.href, "http://example.com/");
Assert.equal(bm2.title, "a bookmark");
});
add_task(function* remove_bookmark_orphans() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark" });
checkBookmarkObject(bm1);
PlacesUtils.annotations.setItemAnnotation((yield PlacesUtils.promiseItemId(bm1.guid)),
"testanno", "testvalue", 0, 0);
let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
checkBookmarkObject(bm2);
// Check there are no orphan annotations.
let conn = yield PlacesUtils.promiseDBConnection();
let annoAttrs = yield conn.execute(`SELECT id, name FROM moz_anno_attributes`);
// Bug 1306445 will eventually remove the mobile root anno.
Assert.equal(annoAttrs.length, 1);
Assert.equal(annoAttrs[0].getResultByName("name"), PlacesUtils.MOBILE_ROOT_ANNO);
let annos = rows = yield conn.execute(`SELECT item_id, anno_attribute_id FROM moz_items_annos`);
Assert.equal(annos.length, 1);
Assert.equal(annos[0].getResultByName("item_id"), PlacesUtils.mobileFolderId);
Assert.equal(annos[0].getResultByName("anno_attribute_id"), annoAttrs[0].getResultByName("id"));
});
add_task(function* remove_bookmark_empty_title() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "" });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.index, 0);
Assert.ok(!("title" in bm2));
});
add_task(function* remove_folder() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_FOLDER);
Assert.equal(bm2.title, "a folder");
Assert.ok(!("url" in bm2));
});
add_task(function* test_nested_contents_removed() {
let folder1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
let folder2 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder1.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
let sep = yield PlacesUtils.bookmarks.insert({ parentGuid: folder2.guid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
yield PlacesUtils.bookmarks.remove(folder1);
Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(folder1.guid)), null);
Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(folder2.guid)), null);
Assert.strictEqual((yield PlacesUtils.bookmarks.fetch(sep.guid)), null);
});
add_task(function* remove_folder_empty_title() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "" });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.index, 0);
Assert.ok(!("title" in bm2));
});
add_task(function* remove_separator() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
checkBookmarkObject(bm1);
let bm2 = yield PlacesUtils.bookmarks.remove(bm1.guid);
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
Assert.ok(!("url" in bm2));
Assert.ok(!("title" in bm2));
});
add_task(function* test_nested_content_fails_when_not_allowed() {
let folder1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
yield PlacesUtils.bookmarks.insert({ parentGuid: folder1.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
yield Assert.rejects(PlacesUtils.bookmarks.remove(folder1, {preventRemovalOfNonEmptyFolders: true}),
/Cannot remove a non-empty folder./);
});
function run_test() {
run_next_test();
}

View file

@ -0,0 +1,177 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function* invalid_input_throws() {
Assert.throws(() => PlacesUtils.bookmarks.reorder(),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.reorder(null),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.reorder("test"),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.reorder(123),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.reorder({ guid: "test" }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012"),
/Must provide a sorted array of children GUIDs./);
Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", {}),
/Must provide a sorted array of children GUIDs./);
Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", null),
/Must provide a sorted array of children GUIDs./);
Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", []),
/Must provide a sorted array of children GUIDs./);
Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ null ]),
/Invalid GUID found in the sorted children array/);
Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ "" ]),
/Invalid GUID found in the sorted children array/);
Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ {} ]),
/Invalid GUID found in the sorted children array/);
Assert.throws(() => PlacesUtils.bookmarks.reorder("123456789012", [ "012345678901", null ]),
/Invalid GUID found in the sorted children array/);
});
add_task(function* reorder_nonexistent_guid() {
yield Assert.rejects(PlacesUtils.bookmarks.reorder("123456789012", [ "012345678901" ]),
/No folder found for the provided GUID/,
"Should throw for nonexisting guid");
});
add_task(function* reorder() {
let bookmarks = [
{ url: "http://example1.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid
},
{ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid
},
{ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
parentGuid: PlacesUtils.bookmarks.unfiledGuid
},
{ url: "http://example2.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid
},
{ url: "http://example3.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid
}
];
let sorted = [];
for (let bm of bookmarks) {
sorted.push(yield PlacesUtils.bookmarks.insert(bm));
}
// Check the initial append sorting.
Assert.ok(sorted.every((bm, i) => bm.index == i),
"Initial bookmarks sorting is correct");
// Apply random sorting and run multiple tests.
for (let t = 0; t < 4; t++) {
sorted.sort(() => 0.5 - Math.random());
let sortedGuids = sorted.map(child => child.guid);
dump("Expected order: " + sortedGuids.join() + "\n");
// Add a nonexisting guid to the array, to ensure nothing will break.
sortedGuids.push("123456789012");
yield PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.unfiledGuid,
sortedGuids);
for (let i = 0; i < sorted.length; ++i) {
let item = yield PlacesUtils.bookmarks.fetch(sorted[i].guid);
Assert.equal(item.index, i);
}
}
do_print("Test partial sorting");
// Try a partial sorting by passing only 2 entries.
// The unspecified entries should retain the original order.
sorted = [ sorted[1], sorted[0] ].concat(sorted.slice(2));
let sortedGuids = [ sorted[0].guid, sorted[1].guid ];
dump("Expected order: " + sorted.map(b => b.guid).join() + "\n");
yield PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.unfiledGuid,
sortedGuids);
for (let i = 0; i < sorted.length; ++i) {
let item = yield PlacesUtils.bookmarks.fetch(sorted[i].guid);
Assert.equal(item.index, i);
}
// Use triangular numbers to detect skipped position.
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.execute(
`SELECT parent
FROM moz_bookmarks
GROUP BY parent
HAVING (SUM(DISTINCT position + 1) - (count(*) * (count(*) + 1) / 2)) <> 0`);
Assert.equal(rows.length, 0, "All the bookmarks should have consistent positions");
});
add_task(function* move_and_reorder() {
// Start clean.
yield PlacesUtils.bookmarks.eraseEverything();
let bm1 = yield PlacesUtils.bookmarks.insert({
url: "http://example1.com/",
parentGuid: PlacesUtils.bookmarks.unfiledGuid
});
let f1 = yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid
});
let bm2 = yield PlacesUtils.bookmarks.insert({
url: "http://example2.com/",
parentGuid: f1.guid
});
let f2 = yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid
});
let bm3 = yield PlacesUtils.bookmarks.insert({
url: "http://example3.com/",
parentGuid: f2.guid
});
let bm4 = yield PlacesUtils.bookmarks.insert({
url: "http://example4.com/",
parentGuid: f2.guid
});
let bm5 = yield PlacesUtils.bookmarks.insert({
url: "http://example5.com/",
parentGuid: f2.guid
});
// Invert f2 children.
// This is critical to reproduce the bug, cause it inverts the position
// compared to the natural insertion order.
yield PlacesUtils.bookmarks.reorder(f2.guid, [bm5.guid, bm4.guid, bm3.guid]);
bm1.parentGuid = f1.guid;
bm1.index = 0;
yield PlacesUtils.bookmarks.update(bm1);
bm1 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
Assert.equal(bm1.index, 0);
bm2 = yield PlacesUtils.bookmarks.fetch(bm2.guid);
Assert.equal(bm2.index, 1);
bm3 = yield PlacesUtils.bookmarks.fetch(bm3.guid);
Assert.equal(bm3.index, 2);
bm4 = yield PlacesUtils.bookmarks.fetch(bm4.guid);
Assert.equal(bm4.index, 1);
bm5 = yield PlacesUtils.bookmarks.fetch(bm5.guid);
Assert.equal(bm5.index, 0);
// No-op reorder on f1 children.
// Nothing should change. Though, due to bug 1293365 this was causing children
// of other folders to get messed up.
yield PlacesUtils.bookmarks.reorder(f1.guid, [bm1.guid, bm2.guid]);
bm1 = yield PlacesUtils.bookmarks.fetch(bm1.guid);
Assert.equal(bm1.index, 0);
bm2 = yield PlacesUtils.bookmarks.fetch(bm2.guid);
Assert.equal(bm2.index, 1);
bm3 = yield PlacesUtils.bookmarks.fetch(bm3.guid);
Assert.equal(bm3.index, 2);
bm4 = yield PlacesUtils.bookmarks.fetch(bm4.guid);
Assert.equal(bm4.index, 1);
bm5 = yield PlacesUtils.bookmarks.fetch(bm5.guid);
Assert.equal(bm5.index, 0);
});

View file

@ -0,0 +1,223 @@
add_task(function* invalid_input_throws() {
Assert.throws(() => PlacesUtils.bookmarks.search(),
/Query object is required/);
Assert.throws(() => PlacesUtils.bookmarks.search(null),
/Query object is required/);
Assert.throws(() => PlacesUtils.bookmarks.search({title: 50}),
/Title option must be a string/);
Assert.throws(() => PlacesUtils.bookmarks.search({url: {url: "wombat"}}),
/Url option must be a string or a URL object/);
Assert.throws(() => PlacesUtils.bookmarks.search(50),
/Query must be an object or a string/);
Assert.throws(() => PlacesUtils.bookmarks.search(true),
/Query must be an object or a string/);
});
add_task(function* search_bookmark() {
yield PlacesUtils.bookmarks.eraseEverything();
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/",
title: "another bookmark" });
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid,
url: "http://menu.org/",
title: "an on-menu bookmark" });
let bm4 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
url: "http://toolbar.org/",
title: "an on-toolbar bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
checkBookmarkObject(bm3);
checkBookmarkObject(bm4);
// finds a result by query
let results = yield PlacesUtils.bookmarks.search("example.com");
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
// finds multiple results
results = yield PlacesUtils.bookmarks.search("example");
Assert.equal(results.length, 2);
checkBookmarkObject(results[0]);
checkBookmarkObject(results[1]);
// finds menu bookmarks
results = yield PlacesUtils.bookmarks.search("an on-menu bookmark");
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm3, results[0]);
// finds toolbar bookmarks
results = yield PlacesUtils.bookmarks.search("an on-toolbar bookmark");
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm4, results[0]);
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* search_bookmark_by_query_object() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/",
title: "another bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
let results = yield PlacesUtils.bookmarks.search({query: "example.com"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
results = yield PlacesUtils.bookmarks.search({query: "example"});
Assert.equal(results.length, 2);
checkBookmarkObject(results[0]);
checkBookmarkObject(results[1]);
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* search_bookmark_by_url() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/path",
title: "another bookmark" });
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/path",
title: "third bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
checkBookmarkObject(bm3);
// finds the correct result by url
let results = yield PlacesUtils.bookmarks.search({url: "http://example.com/"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
// normalizes the url
results = yield PlacesUtils.bookmarks.search({url: "http:/example.com"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
// returns multiple matches
results = yield PlacesUtils.bookmarks.search({url: "http://example.org/path"});
Assert.equal(results.length, 2);
// requires exact match
results = yield PlacesUtils.bookmarks.search({url: "http://example.org/"});
Assert.equal(results.length, 0);
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* search_bookmark_by_title() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/path",
title: "another bookmark" });
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.net/",
title: "another bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
checkBookmarkObject(bm3);
// finds the correct result by title
let results = yield PlacesUtils.bookmarks.search({title: "a bookmark"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
// returns multiple matches
results = yield PlacesUtils.bookmarks.search({title: "another bookmark"});
Assert.equal(results.length, 2);
// requires exact match
results = yield PlacesUtils.bookmarks.search({title: "bookmark"});
Assert.equal(results.length, 0);
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* search_bookmark_combinations() {
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/",
title: "a bookmark" });
let bm2 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.org/path",
title: "another bookmark" });
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.net/",
title: "third bookmark" });
checkBookmarkObject(bm1);
checkBookmarkObject(bm2);
checkBookmarkObject(bm3);
// finds the correct result if title and url match
let results = yield PlacesUtils.bookmarks.search({url: "http://example.com/", title: "a bookmark"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm1, results[0]);
// does not match if query is not matching but url and title match
results = yield PlacesUtils.bookmarks.search({url: "http://example.com/", title: "a bookmark", query: "nonexistent"});
Assert.equal(results.length, 0);
// does not match if one parameter is not matching
results = yield PlacesUtils.bookmarks.search({url: "http://what.ever", title: "a bookmark"});
Assert.equal(results.length, 0);
// query only matches if other fields match as well
results = yield PlacesUtils.bookmarks.search({query: "bookmark", url: "http://example.net/"});
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm3, results[0]);
// non-matching query will also return no results
results = yield PlacesUtils.bookmarks.search({query: "nonexistent", url: "http://example.net/"});
Assert.equal(results.length, 0);
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* search_folder() {
let folder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a test folder" });
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: folder.guid,
url: "http://example.com/",
title: "a bookmark" });
checkBookmarkObject(folder);
checkBookmarkObject(bm);
// also finds folders
let results = yield PlacesUtils.bookmarks.search("a test folder");
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.equal(folder.title, results[0].title);
Assert.equal(folder.type, results[0].type);
Assert.equal(folder.parentGuid, results[0].parentGuid);
// finds elements in folders
results = yield PlacesUtils.bookmarks.search("example.com");
Assert.equal(results.length, 1);
checkBookmarkObject(results[0]);
Assert.deepEqual(bm, results[0]);
Assert.equal(folder.guid, results[0].parentGuid);
yield PlacesUtils.bookmarks.eraseEverything();
});

View file

@ -0,0 +1,414 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function* invalid_input_throws() {
Assert.throws(() => PlacesUtils.bookmarks.update(),
/Input should be a valid object/);
Assert.throws(() => PlacesUtils.bookmarks.update(null),
/Input should be a valid object/);
Assert.throws(() => PlacesUtils.bookmarks.update({}),
/The following properties were expected/);
Assert.throws(() => PlacesUtils.bookmarks.update({ guid: "test" }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ guid: null }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ guid: 123 }),
/Invalid value for property 'guid'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ parentGuid: "test" }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ parentGuid: null }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ parentGuid: 123 }),
/Invalid value for property 'parentGuid'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ index: "1" }),
/Invalid value for property 'index'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ index: -10 }),
/Invalid value for property 'index'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ dateAdded: -10 }),
/Invalid value for property 'dateAdded'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ dateAdded: "today" }),
/Invalid value for property 'dateAdded'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ dateAdded: Date.now() }),
/Invalid value for property 'dateAdded'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ lastModified: -10 }),
/Invalid value for property 'lastModified'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ lastModified: "today" }),
/Invalid value for property 'lastModified'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ lastModified: Date.now() }),
/Invalid value for property 'lastModified'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ type: -1 }),
/Invalid value for property 'type'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ type: 100 }),
/Invalid value for property 'type'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ type: "bookmark" }),
/Invalid value for property 'type'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ url: 10 }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ url: "http://te st" }),
/Invalid value for property 'url'/);
let longurl = "http://www.example.com/";
for (let i = 0; i < 65536; i++) {
longurl += "a";
}
Assert.throws(() => PlacesUtils.bookmarks.update({ url: longurl }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ url: NetUtil.newURI(longurl) }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ url: "te st" }),
/Invalid value for property 'url'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ title: -1 }),
/Invalid value for property 'title'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ title: {} }),
/Invalid value for property 'title'/);
Assert.throws(() => PlacesUtils.bookmarks.update({ guid: "123456789012" }),
/Not enough properties to update/);
Assert.throws(() => PlacesUtils.bookmarks.update({ guid: "123456789012",
parentGuid: "012345678901" }),
/The following properties were expected: index/);
});
add_task(function* nonexisting_bookmark_throws() {
try {
yield PlacesUtils.bookmarks.update({ guid: "123456789012",
title: "test" });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/No bookmarks found for the provided GUID/.test(ex));
}
});
add_task(function* invalid_properties_for_existing_bookmark() {
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/" });
try {
yield PlacesUtils.bookmarks.update({ guid: bm.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/The bookmark type cannot be changed/.test(ex));
}
try {
yield PlacesUtils.bookmarks.update({ guid: bm.guid,
dateAdded: new Date() });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/The bookmark dateAdded cannot be changed/.test(ex));
}
try {
yield PlacesUtils.bookmarks.update({ guid: bm.guid,
dateAdded: new Date() });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/The bookmark dateAdded cannot be changed/.test(ex));
}
try {
yield PlacesUtils.bookmarks.update({ guid: bm.guid,
parentGuid: "123456789012",
index: 1 });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/No bookmarks found for the provided parentGuid/.test(ex));
}
let past = new Date(Date.now() - 86400000);
try {
yield PlacesUtils.bookmarks.update({ guid: bm.guid,
lastModified: past });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/Invalid value for property 'lastModified'/.test(ex));
}
let folder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
try {
yield PlacesUtils.bookmarks.update({ guid: folder.guid,
url: "http://example.com/" });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/Invalid value for property 'url'/.test(ex));
}
let separator = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
try {
yield PlacesUtils.bookmarks.update({ guid: separator.guid,
url: "http://example.com/" });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/Invalid value for property 'url'/.test(ex));
}
try {
yield PlacesUtils.bookmarks.update({ guid: separator.guid,
title: "test" });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/Invalid value for property 'title'/.test(ex));
}
});
add_task(function* long_title_trim() {
let longtitle = "a";
for (let i = 0; i < 4096; i++) {
longtitle += "a";
}
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "title" });
checkBookmarkObject(bm);
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
title: longtitle });
let newTitle = bm.title;
Assert.equal(newTitle.length, 4096, "title should have been trimmed");
bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
Assert.equal(bm.title, newTitle);
});
add_task(function* update_lastModified() {
let yesterday = new Date(Date.now() - 86400000);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "title",
dateAdded: yesterday });
checkBookmarkObject(bm);
Assert.deepEqual(bm.lastModified, yesterday);
let time = new Date();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
lastModified: time });
checkBookmarkObject(bm);
Assert.deepEqual(bm.lastModified, time);
bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
Assert.deepEqual(bm.lastModified, time);
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
lastModified: yesterday });
Assert.deepEqual(bm.lastModified, yesterday);
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
title: "title2" });
Assert.ok(bm.lastModified >= time);
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
title: "" });
Assert.ok(!("title" in bm));
bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
Assert.ok(!("title" in bm));
});
add_task(function* update_url() {
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "title" });
checkBookmarkObject(bm);
let lastModified = bm.lastModified;
let frecency = frecencyForUrl(bm.url);
Assert.ok(frecency > 0, "Check frecency has been updated");
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
url: "http://mozilla.org/" });
checkBookmarkObject(bm);
Assert.ok(bm.lastModified >= lastModified);
Assert.equal(bm.url.href, "http://mozilla.org/");
bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
Assert.equal(bm.url.href, "http://mozilla.org/");
Assert.ok(bm.lastModified >= lastModified);
Assert.equal(frecencyForUrl("http://example.com/"), frecency, "Check frecency for example.com");
Assert.equal(frecencyForUrl("http://mozilla.org/"), frecency, "Check frecency for mozilla.org");
});
add_task(function* update_index() {
let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER }) ;
let f1 = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
Assert.equal(f1.index, 0);
let f2 = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
Assert.equal(f2.index, 1);
let f3 = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
Assert.equal(f3.index, 2);
let lastModified = f1.lastModified;
f1 = yield PlacesUtils.bookmarks.update({ guid: f1.guid,
parentGuid: f1.parentGuid,
index: 1});
checkBookmarkObject(f1);
Assert.equal(f1.index, 1);
Assert.ok(f1.lastModified >= lastModified);
parent = yield PlacesUtils.bookmarks.fetch(f1.parentGuid);
Assert.deepEqual(parent.lastModified, f1.lastModified);
f2 = yield PlacesUtils.bookmarks.fetch(f2.guid);
Assert.equal(f2.index, 0);
f3 = yield PlacesUtils.bookmarks.fetch(f3.guid);
Assert.equal(f3.index, 2);
f3 = yield PlacesUtils.bookmarks.update({ guid: f3.guid,
index: 0 });
f1 = yield PlacesUtils.bookmarks.fetch(f1.guid);
Assert.equal(f1.index, 2);
f2 = yield PlacesUtils.bookmarks.fetch(f2.guid);
Assert.equal(f2.index, 1);
});
add_task(function* update_move_folder_into_descendant_throws() {
let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER }) ;
let descendant = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
try {
yield PlacesUtils.bookmarks.update({ guid: parent.guid,
parentGuid: parent.guid,
index: 0 });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/Cannot insert a folder into itself or one of its descendants/.test(ex));
}
try {
yield PlacesUtils.bookmarks.update({ guid: parent.guid,
parentGuid: descendant.guid,
index: 0 });
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/Cannot insert a folder into itself or one of its descendants/.test(ex));
}
});
add_task(function* update_move() {
let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER }) ;
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
url: "http://example.com/",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK }) ;
let descendant = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
Assert.equal(descendant.index, 1);
let lastModified = bm.lastModified;
// This is moving to a nonexisting index by purpose, it will be appended.
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
parentGuid: descendant.guid,
index: 1 });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, descendant.guid);
Assert.equal(bm.index, 0);
Assert.ok(bm.lastModified >= lastModified);
parent = yield PlacesUtils.bookmarks.fetch(parent.guid);
descendant = yield PlacesUtils.bookmarks.fetch(descendant.guid);
Assert.deepEqual(parent.lastModified, bm.lastModified);
Assert.deepEqual(descendant.lastModified, bm.lastModified);
Assert.equal(descendant.index, 0);
bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
Assert.equal(bm.parentGuid, descendant.guid);
Assert.equal(bm.index, 0);
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
parentGuid: parent.guid,
index: 0 });
Assert.equal(bm.parentGuid, parent.guid);
Assert.equal(bm.index, 0);
descendant = yield PlacesUtils.bookmarks.fetch(descendant.guid);
Assert.equal(descendant.index, 1);
});
add_task(function* update_move_append() {
let folder_a =
yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(folder_a);
let folder_b =
yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(folder_b);
/* folder_a: [sep_1, sep_2, sep_3], folder_b: [] */
let sep_1 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder_a.guid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
checkBookmarkObject(sep_1);
let sep_2 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder_a.guid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
checkBookmarkObject(sep_2);
let sep_3 = yield PlacesUtils.bookmarks.insert({ parentGuid: folder_a.guid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
checkBookmarkObject(sep_3);
function ensurePosition(info, parentGuid, index) {
checkBookmarkObject(info);
Assert.equal(info.parentGuid, parentGuid);
Assert.equal(info.index, index);
}
// folder_a: [sep_2, sep_3, sep_1], folder_b: []
sep_1.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
// Note sep_1 includes parentGuid even though we're not moving the item to
// another folder
sep_1 = yield PlacesUtils.bookmarks.update(sep_1);
ensurePosition(sep_1, folder_a.guid, 2);
sep_2 = yield PlacesUtils.bookmarks.fetch(sep_2.guid);
ensurePosition(sep_2, folder_a.guid, 0);
sep_3 = yield PlacesUtils.bookmarks.fetch(sep_3.guid);
ensurePosition(sep_3, folder_a.guid, 1);
sep_1 = yield PlacesUtils.bookmarks.fetch(sep_1.guid);
ensurePosition(sep_1, folder_a.guid, 2);
// folder_a: [sep_2, sep_1], folder_b: [sep_3]
sep_3.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
sep_3.parentGuid = folder_b.guid;
sep_3 = yield PlacesUtils.bookmarks.update(sep_3);
ensurePosition(sep_3, folder_b.guid, 0);
sep_2 = yield PlacesUtils.bookmarks.fetch(sep_2.guid);
ensurePosition(sep_2, folder_a.guid, 0);
sep_1 = yield PlacesUtils.bookmarks.fetch(sep_1.guid);
ensurePosition(sep_1, folder_a.guid, 1);
sep_3 = yield PlacesUtils.bookmarks.fetch(sep_3.guid);
ensurePosition(sep_3, folder_b.guid, 0);
// folder_a: [sep_1], folder_b: [sep_3, sep_2]
sep_2.index = Number.MAX_SAFE_INTEGER;
sep_2.parentGuid = folder_b.guid;
sep_2 = yield PlacesUtils.bookmarks.update(sep_2);
ensurePosition(sep_2, folder_b.guid, 1);
sep_1 = yield PlacesUtils.bookmarks.fetch(sep_1.guid);
ensurePosition(sep_1, folder_a.guid, 0);
sep_3 = yield PlacesUtils.bookmarks.fetch(sep_3.guid);
ensurePosition(sep_3, folder_b.guid, 0);
sep_2 = yield PlacesUtils.bookmarks.fetch(sep_2.guid);
ensurePosition(sep_2, folder_b.guid, 1);
});
function run_test() {
run_next_test();
}

View file

@ -0,0 +1,18 @@
// Bug 1192692 - promiseBookmarksTree caches items without adding observers to
// invalidate the cache.
add_task(function* boookmarks_tree_cache() {
// Note that for this test to be effective, it needs to use the "old" sync
// bookmarks methods - using, eg, PlacesUtils.bookmarks.insert() doesn't
// demonstrate the problem as it indirectly arranges for the observers to
// be added.
let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
uri("http://example.com"),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"A title");
yield PlacesUtils.promiseBookmarksTree();
PlacesUtils.bookmarks.removeItem(id);
yield Assert.rejects(PlacesUtils.promiseItemGuid(id));
});

View file

@ -0,0 +1,68 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
// Get bookmark service
var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
getService(Ci.nsINavBookmarksService);
/**
* Ensures that the Places APIs recognize that aBookmarkedUri is bookmarked
* via aBookmarkId and that aUnbookmarkedUri is not bookmarked at all.
*
* @param aBookmarkId
* an item ID whose corresponding URI is aBookmarkedUri
* @param aBookmarkedUri
* a bookmarked URI that has a corresponding item ID aBookmarkId
* @param aUnbookmarkedUri
* a URI that is not currently bookmarked at all
*/
function checkUris(aBookmarkId, aBookmarkedUri, aUnbookmarkedUri)
{
// Ensure that aBookmarkedUri equals some URI that is bookmarked
var uri = bmsvc.getBookmarkedURIFor(aBookmarkedUri);
do_check_neq(uri, null);
do_check_true(uri.equals(aBookmarkedUri));
// Ensure that aBookmarkedUri is considered bookmarked
do_check_true(bmsvc.isBookmarked(aBookmarkedUri));
// Ensure that the URI corresponding to aBookmarkId equals aBookmarkedUri
do_check_true(bmsvc.getBookmarkURI(aBookmarkId).equals(aBookmarkedUri));
// Ensure that aUnbookmarkedUri does not equal any URI that is bookmarked
uri = bmsvc.getBookmarkedURIFor(aUnbookmarkedUri);
do_check_eq(uri, null);
// Ensure that aUnbookmarkedUri is not considered bookmarked
do_check_false(bmsvc.isBookmarked(aUnbookmarkedUri));
}
// main
function run_test() {
// Create a folder
var folderId = bmsvc.createFolder(bmsvc.toolbarFolder,
"test",
bmsvc.DEFAULT_INDEX);
// Create 2 URIs
var uri1 = uri("http://www.dogs.com");
var uri2 = uri("http://www.cats.com");
// Bookmark the first one
var bookmarkId = bmsvc.insertBookmark(folderId,
uri1,
bmsvc.DEFAULT_INDEX,
"Dogs");
// uri1 is bookmarked via bookmarkId, uri2 is not
checkUris(bookmarkId, uri1, uri2);
// Change the URI of the bookmark to uri2
bmsvc.changeBookmarkURI(bookmarkId, uri2);
// uri2 is now bookmarked via bookmarkId, uri1 is not
checkUris(bookmarkId, uri2, uri1);
}

View file

@ -0,0 +1,84 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* Test bookmarksService.getBookmarkedURIFor(aURI);
*/
var hs = PlacesUtils.history;
var bs = PlacesUtils.bookmarks;
function run_test() {
run_next_test();
}
add_task(function* test_getBookmarkedURIFor() {
let now = Date.now() * 1000;
const sourceURI = uri("http://test.mozilla.org/");
// Add a visit and a bookmark.
yield PlacesTestUtils.addVisits({ uri: sourceURI, visitDate: now });
do_check_eq(bs.getBookmarkedURIFor(sourceURI), null);
let sourceItemId = bs.insertBookmark(bs.unfiledBookmarksFolder,
sourceURI,
bs.DEFAULT_INDEX,
"bookmark");
do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
// Add a redirected visit.
const permaURI = uri("http://perma.mozilla.org/");
yield PlacesTestUtils.addVisits({
uri: permaURI,
transition: TRANSITION_REDIRECT_PERMANENT,
visitDate: now++,
referrer: sourceURI
});
do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
do_check_true(bs.getBookmarkedURIFor(permaURI).equals(sourceURI));
// Add a bookmark to the destination.
let permaItemId = bs.insertBookmark(bs.unfiledBookmarksFolder,
permaURI,
bs.DEFAULT_INDEX,
"bookmark");
do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
do_check_true(bs.getBookmarkedURIFor(permaURI).equals(permaURI));
// Now remove the bookmark on the destination.
bs.removeItem(permaItemId);
// We should see the source as bookmark.
do_check_true(bs.getBookmarkedURIFor(permaURI).equals(sourceURI));
// Add another redirected visit.
const tempURI = uri("http://perma.mozilla.org/");
yield PlacesTestUtils.addVisits({
uri: tempURI,
transition: TRANSITION_REDIRECT_TEMPORARY,
visitDate: now++,
referrer: permaURI
});
do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
do_check_true(bs.getBookmarkedURIFor(tempURI).equals(sourceURI));
// Add a bookmark to the destination.
let tempItemId = bs.insertBookmark(bs.unfiledBookmarksFolder,
tempURI,
bs.DEFAULT_INDEX,
"bookmark");
do_check_true(bs.getBookmarkedURIFor(sourceURI).equals(sourceURI));
do_check_true(bs.getBookmarkedURIFor(tempURI).equals(tempURI));
// Now remove the bookmark on the destination.
bs.removeItem(tempItemId);
// We should see the source as bookmark.
do_check_true(bs.getBookmarkedURIFor(tempURI).equals(sourceURI));
// Remove the source bookmark as well.
bs.removeItem(sourceItemId);
do_check_eq(bs.getBookmarkedURIFor(tempURI), null);
// Try to pass in a never seen URI, should return null and a new entry should
// not be added to the database.
do_check_eq(bs.getBookmarkedURIFor(uri("http://does.not.exist/")), null);
do_check_false(page_in_database("http://does.not.exist/"));
});

View file

@ -0,0 +1,310 @@
const URI1 = NetUtil.newURI("http://test1.mozilla.org/");
const URI2 = NetUtil.newURI("http://test2.mozilla.org/");
const URI3 = NetUtil.newURI("http://test3.mozilla.org/");
function check_keyword(aURI, aKeyword) {
if (aKeyword)
aKeyword = aKeyword.toLowerCase();
for (let bm of PlacesUtils.getBookmarksForURI(aURI)) {
let keyword = PlacesUtils.bookmarks.getKeywordForBookmark(bm);
if (keyword && !aKeyword) {
throw (`${aURI.spec} should not have a keyword`);
} else if (aKeyword && keyword == aKeyword) {
Assert.equal(keyword, aKeyword);
}
}
if (aKeyword) {
let uri = PlacesUtils.bookmarks.getURIForKeyword(aKeyword);
Assert.equal(uri.spec, aURI.spec);
// Check case insensitivity.
uri = PlacesUtils.bookmarks.getURIForKeyword(aKeyword.toUpperCase());
Assert.equal(uri.spec, aURI.spec);
}
}
function* check_orphans() {
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.executeCached(
`SELECT id FROM moz_keywords k
WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = k.place_id)
`);
Assert.equal(rows.length, 0);
}
function expectNotifications() {
let notifications = [];
let observer = new Proxy(NavBookmarkObserver, {
get(target, name) {
if (name == "check") {
PlacesUtils.bookmarks.removeObserver(observer);
return expectedNotifications =>
Assert.deepEqual(notifications, expectedNotifications);
}
if (name.startsWith("onItemChanged")) {
return function(id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid, oldVal) {
if (prop != "keyword")
return;
let args = Array.from(arguments, arg => {
if (arg && arg instanceof Ci.nsIURI)
return new URL(arg.spec);
if (arg && typeof(arg) == "number" && arg >= Date.now() * 1000)
return new Date(parseInt(arg/1000));
return arg;
});
notifications.push({ name: name, arguments: args });
}
}
return target[name];
}
});
PlacesUtils.bookmarks.addObserver(observer, false);
return observer;
}
add_task(function test_invalid_input() {
Assert.throws(() => PlacesUtils.bookmarks.getURIForKeyword(null),
/NS_ERROR_ILLEGAL_VALUE/);
Assert.throws(() => PlacesUtils.bookmarks.getURIForKeyword(""),
/NS_ERROR_ILLEGAL_VALUE/);
Assert.throws(() => PlacesUtils.bookmarks.getKeywordForBookmark(null),
/NS_ERROR_ILLEGAL_VALUE/);
Assert.throws(() => PlacesUtils.bookmarks.getKeywordForBookmark(0),
/NS_ERROR_ILLEGAL_VALUE/);
Assert.throws(() => PlacesUtils.bookmarks.setKeywordForBookmark(null, "k"),
/NS_ERROR_ILLEGAL_VALUE/);
Assert.throws(() => PlacesUtils.bookmarks.setKeywordForBookmark(0, "k"),
/NS_ERROR_ILLEGAL_VALUE/);
});
add_task(function* test_addBookmarkAndKeyword() {
check_keyword(URI1, null);
let fc = yield foreign_count(URI1);
let observer = expectNotifications();
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI1,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test");
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
let bookmark = yield PlacesUtils.bookmarks.fetch({ url: URI1 });
observer.check([ { name: "onItemChanged",
arguments: [ itemId, "keyword", false, "keyword",
bookmark.lastModified, bookmark.type,
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
bookmark.guid, bookmark.parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
yield PlacesTestUtils.promiseAsyncUpdates();
check_keyword(URI1, "keyword");
Assert.equal((yield foreign_count(URI1)), fc + 2); // + 1 bookmark + 1 keyword
yield PlacesTestUtils.promiseAsyncUpdates();
yield check_orphans();
});
add_task(function* test_addBookmarkToURIHavingKeyword() {
// The uri has already a keyword.
check_keyword(URI1, "keyword");
let fc = yield foreign_count(URI1);
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI1,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test");
check_keyword(URI1, "keyword");
Assert.equal((yield foreign_count(URI1)), fc + 1); // + 1 bookmark
PlacesUtils.bookmarks.removeItem(itemId);
yield PlacesTestUtils.promiseAsyncUpdates();
check_orphans();
});
add_task(function* test_sameKeywordDifferentURI() {
let fc1 = yield foreign_count(URI1);
let fc2 = yield foreign_count(URI2);
let observer = expectNotifications();
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI2,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test2");
check_keyword(URI1, "keyword");
check_keyword(URI2, null);
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "kEyWoRd");
let bookmark1 = yield PlacesUtils.bookmarks.fetch({ url: URI1 });
let bookmark2 = yield PlacesUtils.bookmarks.fetch({ url: URI2 });
observer.check([ { name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
"keyword", false, "",
bookmark1.lastModified, bookmark1.type,
(yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
bookmark1.guid, bookmark1.parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemChanged",
arguments: [ itemId, "keyword", false, "keyword",
bookmark2.lastModified, bookmark2.type,
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
bookmark2.guid, bookmark2.parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
yield PlacesTestUtils.promiseAsyncUpdates();
// The keyword should have been "moved" to the new URI.
check_keyword(URI1, null);
Assert.equal((yield foreign_count(URI1)), fc1 - 1); // - 1 keyword
check_keyword(URI2, "keyword");
Assert.equal((yield foreign_count(URI2)), fc2 + 2); // + 1 bookmark + 1 keyword
yield PlacesTestUtils.promiseAsyncUpdates();
check_orphans();
});
add_task(function* test_sameURIDifferentKeyword() {
let fc = yield foreign_count(URI2);
let observer = expectNotifications();
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI2,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test2");
check_keyword(URI2, "keyword");
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword2");
let bookmarks = [];
yield PlacesUtils.bookmarks.fetch({ url: URI2 }, bookmark => bookmarks.push(bookmark));
observer.check([ { name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[0].guid)),
"keyword", false, "keyword2",
bookmarks[0].lastModified, bookmarks[0].type,
(yield PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
bookmarks[0].guid, bookmarks[0].parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[1].guid)),
"keyword", false, "keyword2",
bookmarks[1].lastModified, bookmarks[1].type,
(yield PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
bookmarks[1].guid, bookmarks[1].parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
yield PlacesTestUtils.promiseAsyncUpdates();
check_keyword(URI2, "keyword2");
Assert.equal((yield foreign_count(URI2)), fc + 2); // + 1 bookmark + 1 keyword
yield PlacesTestUtils.promiseAsyncUpdates();
check_orphans();
});
add_task(function* test_removeBookmarkWithKeyword() {
let fc = yield foreign_count(URI2);
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI2,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test");
// The keyword should not be removed, since there are other bookmarks yet.
PlacesUtils.bookmarks.removeItem(itemId);
check_keyword(URI2, "keyword2");
Assert.equal((yield foreign_count(URI2)), fc); // + 1 bookmark - 1 bookmark
yield PlacesTestUtils.promiseAsyncUpdates();
check_orphans();
});
add_task(function* test_unsetKeyword() {
let fc = yield foreign_count(URI2);
let observer = expectNotifications();
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI2,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test");
// The keyword should be removed from any bookmark.
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, null);
let bookmarks = [];
yield PlacesUtils.bookmarks.fetch({ url: URI2 }, bookmark => bookmarks.push(bookmark));
do_print(bookmarks.length);
observer.check([ { name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[0].guid)),
"keyword", false, "",
bookmarks[0].lastModified, bookmarks[0].type,
(yield PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
bookmarks[0].guid, bookmarks[0].parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[1].guid)),
"keyword", false, "",
bookmarks[1].lastModified, bookmarks[1].type,
(yield PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
bookmarks[1].guid, bookmarks[1].parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] },
{ name: "onItemChanged",
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[2].guid)),
"keyword", false, "",
bookmarks[2].lastModified, bookmarks[2].type,
(yield PlacesUtils.promiseItemId(bookmarks[2].parentGuid)),
bookmarks[2].guid, bookmarks[2].parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
check_keyword(URI1, null);
check_keyword(URI2, null);
Assert.equal((yield foreign_count(URI2)), fc - 1); // + 1 bookmark - 2 keyword
yield PlacesTestUtils.promiseAsyncUpdates();
check_orphans();
});
add_task(function* test_addRemoveBookmark() {
let observer = expectNotifications();
let itemId =
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
URI3,
PlacesUtils.bookmarks.DEFAULT_INDEX,
"test3");
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
let bookmark = yield PlacesUtils.bookmarks.fetch({ url: URI3 });
let parentId = yield PlacesUtils.promiseItemId(bookmark.parentGuid);
PlacesUtils.bookmarks.removeItem(itemId);
observer.check([ { name: "onItemChanged",
arguments: [ itemId,
"keyword", false, "keyword",
bookmark.lastModified, bookmark.type,
parentId,
bookmark.guid, bookmark.parentGuid, "",
Ci.nsINavBookmarksService.SOURCE_DEFAULT ] }
]);
check_keyword(URI3, null);
// Don't check the foreign count since the process is async.
// The new test_keywords.js in unit is checking this though.
yield PlacesTestUtils.promiseAsyncUpdates();
check_orphans();
});
function run_test() {
run_next_test();
}

View file

@ -0,0 +1,640 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that each nsINavBookmarksObserver method gets the correct input.
Cu.import("resource://gre/modules/PromiseUtils.jsm");
const GUID_RE = /^[a-zA-Z0-9\-_]{12}$/;
var gBookmarksObserver = {
expected: [],
setup(expected) {
this.expected = expected;
this.deferred = PromiseUtils.defer();
return this.deferred.promise;
},
validate: function (aMethodName, aArguments) {
do_check_eq(this.expected[0].name, aMethodName);
let args = this.expected.shift().args;
do_check_eq(aArguments.length, args.length);
for (let i = 0; i < aArguments.length; i++) {
do_check_true(args[i].check(aArguments[i]), aMethodName + "(args[" + i + "]: " + args[i].name + ")");
}
if (this.expected.length === 0) {
this.deferred.resolve();
}
},
// nsINavBookmarkObserver
onBeginUpdateBatch() {
return this.validate("onBeginUpdateBatch", arguments);
},
onEndUpdateBatch() {
return this.validate("onEndUpdateBatch", arguments);
},
onItemAdded() {
return this.validate("onItemAdded", arguments);
},
onItemRemoved() {
return this.validate("onItemRemoved", arguments);
},
onItemChanged() {
return this.validate("onItemChanged", arguments);
},
onItemVisited() {
return this.validate("onItemVisited", arguments);
},
onItemMoved() {
return this.validate("onItemMoved", arguments);
},
// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]),
};
var gBookmarkSkipObserver = {
skipTags: true,
skipDescendantsOnItemRemoval: true,
expected: null,
setup(expected) {
this.expected = expected;
this.deferred = PromiseUtils.defer();
return this.deferred.promise;
},
validate: function (aMethodName) {
do_check_eq(this.expected.shift(), aMethodName);
if (this.expected.length === 0) {
this.deferred.resolve();
}
},
// nsINavBookmarkObserver
onBeginUpdateBatch() {
return this.validate("onBeginUpdateBatch", arguments);
},
onEndUpdateBatch() {
return this.validate("onEndUpdateBatch", arguments);
},
onItemAdded() {
return this.validate("onItemAdded", arguments);
},
onItemRemoved() {
return this.validate("onItemRemoved", arguments);
},
onItemChanged() {
return this.validate("onItemChanged", arguments);
},
onItemVisited() {
return this.validate("onItemVisited", arguments);
},
onItemMoved() {
return this.validate("onItemMoved", arguments);
},
// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]),
};
add_task(function setup() {
PlacesUtils.bookmarks.addObserver(gBookmarksObserver, false);
PlacesUtils.bookmarks.addObserver(gBookmarkSkipObserver, false);
});
add_task(function* batch() {
let promise = Promise.all([
gBookmarksObserver.setup([
{ name: "onBeginUpdateBatch",
args: [] },
{ name: "onEndUpdateBatch",
args: [] },
]),
gBookmarkSkipObserver.setup([
"onBeginUpdateBatch", "onEndUpdateBatch"
])]);
PlacesUtils.bookmarks.runInBatchMode({
runBatched: function () {
// Nothing.
}
}, null);
yield promise;
});
add_task(function* onItemAdded_bookmark() {
const TITLE = "Bookmark 1";
let uri = NetUtil.newURI("http://1.mozilla.org/");
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemAdded"
]),
gBookmarksObserver.setup([
{ name: "onItemAdded",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
{ name: "title", check: v => v === TITLE },
{ name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
])]);
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
uri, PlacesUtils.bookmarks.DEFAULT_INDEX,
TITLE);
yield promise;
});
add_task(function* onItemAdded_separator() {
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemAdded"
]),
gBookmarksObserver.setup([
{ name: "onItemAdded",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "index", check: v => v === 1 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_SEPARATOR },
{ name: "uri", check: v => v === null },
{ name: "title", check: v => v === null },
{ name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
])]);
PlacesUtils.bookmarks.insertSeparator(PlacesUtils.unfiledBookmarksFolderId,
PlacesUtils.bookmarks.DEFAULT_INDEX);
yield promise;
});
add_task(function* onItemAdded_folder() {
const TITLE = "Folder 1";
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemAdded"
]),
gBookmarksObserver.setup([
{ name: "onItemAdded",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "index", check: v => v === 2 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
{ name: "uri", check: v => v === null },
{ name: "title", check: v => v === TITLE },
{ name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
])]);
PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId,
TITLE,
PlacesUtils.bookmarks.DEFAULT_INDEX);
yield promise;
});
add_task(function* onItemChanged_title_bookmark() {
let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
const TITLE = "New title";
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemChanged"
]),
gBookmarksObserver.setup([
{ name: "onItemChanged", // This is an unfortunate effect of bug 653910.
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "property", check: v => v === "title" },
{ name: "isAnno", check: v => v === false },
{ name: "newValue", check: v => v === TITLE },
{ name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "oldValue", check: v => typeof(v) == "string" },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
])]);
PlacesUtils.bookmarks.setItemTitle(id, TITLE);
yield promise;
});
add_task(function* onItemChanged_tags_bookmark() {
let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
const TAG = "tag";
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemChanged", "onItemChanged"
]),
gBookmarksObserver.setup([
{ name: "onItemAdded", // This is the tag folder.
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => v === PlacesUtils.tagsFolderId },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
{ name: "uri", check: v => v === null },
{ name: "title", check: v => v === TAG },
{ name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemAdded", // This is the tag.
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
{ name: "title", check: v => v === null },
{ name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemChanged",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "property", check: v => v === "tags" },
{ name: "isAnno", check: v => v === false },
{ name: "newValue", check: v => v === "" },
{ name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "oldValue", check: v => typeof(v) == "string" },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemRemoved", // This is the tag.
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemRemoved", // This is the tag folder.
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => v === PlacesUtils.tagsFolderId },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
{ name: "uri", check: v => v === null },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemChanged",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "property", check: v => v === "tags" },
{ name: "isAnno", check: v => v === false },
{ name: "newValue", check: v => v === "" },
{ name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "oldValue", check: v => typeof(v) == "string" },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
])]);
PlacesUtils.tagging.tagURI(uri, [TAG]);
PlacesUtils.tagging.untagURI(uri, [TAG]);
yield promise;
});
add_task(function* onItemMoved_bookmark() {
let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemMoved", "onItemMoved"
]),
gBookmarksObserver.setup([
{ name: "onItemMoved",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "oldParentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "oldIndex", check: v => v === 0 },
{ name: "newParentId", check: v => v === PlacesUtils.toolbarFolderId },
{ name: "newIndex", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "oldParentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "newParentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemMoved",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "oldParentId", check: v => v === PlacesUtils.toolbarFolderId },
{ name: "oldIndex", check: v => v === 0 },
{ name: "newParentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "newIndex", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "oldParentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "newParentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
])]);
PlacesUtils.bookmarks.moveItem(id, PlacesUtils.toolbarFolderId, 0);
PlacesUtils.bookmarks.moveItem(id, PlacesUtils.unfiledBookmarksFolderId, 0);
yield promise;
});
add_task(function* onItemMoved_bookmark() {
let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemVisited"
]),
gBookmarksObserver.setup([
{ name: "onItemVisited",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "visitId", check: v => typeof(v) == "number" && v > 0 },
{ name: "time", check: v => typeof(v) == "number" && v > 0 },
{ name: "transitionType", check: v => v === PlacesUtils.history.TRANSITION_TYPED },
{ name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
{ name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
] },
])]);
PlacesTestUtils.addVisits({ uri: uri, transition: TRANSITION_TYPED });
yield promise;
});
add_task(function* onItemRemoved_bookmark() {
let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemChanged", "onItemRemoved"
]),
gBookmarksObserver.setup([
{ name: "onItemChanged", // This is an unfortunate effect of bug 653910.
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "property", check: v => v === "" },
{ name: "isAnno", check: v => v === true },
{ name: "newValue", check: v => v === "" },
{ name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "oldValue", check: v => typeof(v) == "string" },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemRemoved",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
])]);
PlacesUtils.bookmarks.removeItem(id);
yield promise;
});
add_task(function* onItemRemoved_separator() {
let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemChanged", "onItemRemoved"
]),
gBookmarksObserver.setup([
{ name: "onItemChanged", // This is an unfortunate effect of bug 653910.
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "property", check: v => v === "" },
{ name: "isAnno", check: v => v === true },
{ name: "newValue", check: v => v === "" },
{ name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_SEPARATOR },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "oldValue", check: v => typeof(v) == "string" },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemRemoved",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_SEPARATOR },
{ name: "uri", check: v => v === null },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
])]);
PlacesUtils.bookmarks.removeItem(id);
yield promise;
});
add_task(function* onItemRemoved_folder() {
let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemChanged", "onItemRemoved"
]),
gBookmarksObserver.setup([
{ name: "onItemChanged", // This is an unfortunate effect of bug 653910.
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "property", check: v => v === "" },
{ name: "isAnno", check: v => v === true },
{ name: "newValue", check: v => v === "" },
{ name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "oldValue", check: v => typeof(v) == "string" },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemRemoved",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
{ name: "uri", check: v => v === null },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
])]);
PlacesUtils.bookmarks.removeItem(id);
yield promise;
});
add_task(function* onItemRemoved_folder_recursive() {
const TITLE = "Folder 3";
const BMTITLE = "Bookmark 1";
let uri = NetUtil.newURI("http://1.mozilla.org/");
let promise = Promise.all([
gBookmarkSkipObserver.setup([
"onItemAdded", "onItemAdded", "onItemAdded", "onItemAdded",
"onItemChanged", "onItemRemoved"
]),
gBookmarksObserver.setup([
{ name: "onItemAdded",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => v === PlacesUtils.unfiledBookmarksFolderId },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
{ name: "uri", check: v => v === null },
{ name: "title", check: v => v === TITLE },
{ name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemAdded",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => v === PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0) },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
{ name: "title", check: v => v === BMTITLE },
{ name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemAdded",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => v === PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0) },
{ name: "index", check: v => v === 1 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
{ name: "uri", check: v => v === null },
{ name: "title", check: v => v === TITLE },
{ name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemAdded",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => v === PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0), 1) },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
{ name: "title", check: v => v === BMTITLE },
{ name: "dateAdded", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemChanged", // This is an unfortunate effect of bug 653910.
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "property", check: v => v === "" },
{ name: "isAnno", check: v => v === true },
{ name: "newValue", check: v => v === "" },
{ name: "lastModified", check: v => typeof(v) == "number" && v > 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "oldValue", check: v => typeof(v) == "string" },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemRemoved",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemRemoved",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "index", check: v => v === 1 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
{ name: "uri", check: v => v === null },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemRemoved",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
{ name: "uri", check: v => v instanceof Ci.nsIURI && v.equals(uri) },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
{ name: "onItemRemoved",
args: [
{ name: "itemId", check: v => typeof(v) == "number" && v > 0 },
{ name: "parentId", check: v => typeof(v) == "number" && v > 0 },
{ name: "index", check: v => v === 0 },
{ name: "itemType", check: v => v === PlacesUtils.bookmarks.TYPE_FOLDER },
{ name: "uri", check: v => v === null },
{ name: "guid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "parentGuid", check: v => typeof(v) == "string" && GUID_RE.test(v) },
{ name: "source", check: v => Object.values(PlacesUtils.bookmarks.SOURCES).includes(v) },
] },
])]);
let folder = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId,
TITLE,
PlacesUtils.bookmarks.DEFAULT_INDEX);
PlacesUtils.bookmarks.insertBookmark(folder,
uri, PlacesUtils.bookmarks.DEFAULT_INDEX,
BMTITLE);
let folder2 = PlacesUtils.bookmarks.createFolder(folder, TITLE,
PlacesUtils.bookmarks.DEFAULT_INDEX);
PlacesUtils.bookmarks.insertBookmark(folder2,
uri, PlacesUtils.bookmarks.DEFAULT_INDEX,
BMTITLE);
PlacesUtils.bookmarks.removeItem(folder);
yield promise;
});
add_task(function cleanup()
{
PlacesUtils.bookmarks.removeObserver(gBookmarksObserver);
PlacesUtils.bookmarks.removeObserver(gBookmarkSkipObserver);
});

View file

@ -0,0 +1,37 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function run_test()
{
const ROOTS = [
PlacesUtils.bookmarksMenuFolderId,
PlacesUtils.toolbarFolderId,
PlacesUtils.unfiledBookmarksFolderId,
PlacesUtils.tagsFolderId,
PlacesUtils.placesRootId,
PlacesUtils.mobileFolderId,
];
for (let root of ROOTS) {
do_check_true(PlacesUtils.isRootItem(root));
try {
PlacesUtils.bookmarks.removeItem(root);
do_throw("Trying to remove a root should throw");
} catch (ex) {}
try {
PlacesUtils.bookmarks.moveItem(root, PlacesUtils.placesRootId, 0);
do_throw("Trying to move a root should throw");
} catch (ex) {}
try {
PlacesUtils.bookmarks.removeFolderChildren(root);
if (root == PlacesUtils.placesRootId)
do_throw("Trying to remove children of the main root should throw");
} catch (ex) {
if (root != PlacesUtils.placesRootId)
do_throw("Trying to remove children of other roots should not throw");
}
}
}

View file

@ -0,0 +1,70 @@
/**
* This test ensures that reinserting a folder within a transaction gives it
* a different GUID, and passes the GUID to the observers.
*/
add_task(function* test_removeFolderTransaction_reinsert() {
let folder = yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.menuGuid,
title: "Test folder",
});
let folderId = yield PlacesUtils.promiseItemId(folder.guid);
let fx = yield PlacesUtils.bookmarks.insert({
parentGuid: folder.guid,
title: "Get Firefox!",
url: "http://getfirefox.com",
});
let fxId = yield PlacesUtils.promiseItemId(fx.guid);
let tb = yield PlacesUtils.bookmarks.insert({
parentGuid: folder.guid,
title: "Get Thunderbird!",
url: "http://getthunderbird.com",
});
let tbId = yield PlacesUtils.promiseItemId(tb.guid);
let notifications = [];
function checkNotifications(expected, message) {
deepEqual(notifications, expected, message);
notifications.length = 0;
}
let observer = {
onItemAdded(itemId, parentId, index, type, uri, title, dateAdded, guid,
parentGuid) {
notifications.push(["onItemAdded", itemId, parentId, guid, parentGuid]);
},
onItemRemoved(itemId, parentId, index, type, uri, guid, parentGuid) {
notifications.push(["onItemRemoved", itemId, parentId, guid, parentGuid]);
},
};
PlacesUtils.bookmarks.addObserver(observer, false);
PlacesUtils.registerShutdownFunction(function() {
PlacesUtils.bookmarks.removeObserver(observer);
});
let transaction = PlacesUtils.bookmarks.getRemoveFolderTransaction(folderId);
deepEqual(notifications, [], "We haven't executed the transaction yet");
transaction.doTransaction();
checkNotifications([
["onItemRemoved", tbId, folderId, tb.guid, folder.guid],
["onItemRemoved", fxId, folderId, fx.guid, folder.guid],
["onItemRemoved", folderId, PlacesUtils.bookmarksMenuFolderId, folder.guid,
PlacesUtils.bookmarks.menuGuid],
], "Executing transaction should remove folder and its descendants");
transaction.undoTransaction();
// At this point, the restored folder has the same ID, but a different GUID.
let newFolderGuid = yield PlacesUtils.promiseItemGuid(folderId);
checkNotifications([
["onItemAdded", folderId, PlacesUtils.bookmarksMenuFolderId, newFolderGuid,
PlacesUtils.bookmarks.menuGuid],
], "Undo should reinsert folder with same ID and different GUID");
transaction.redoTransaction();
checkNotifications([
["onItemRemoved", folderId, PlacesUtils.bookmarksMenuFolderId,
newFolderGuid, PlacesUtils.bookmarks.menuGuid],
], "Redo should forward new GUID to observer");
});

View file

@ -0,0 +1,30 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
var tests = [];
const DEFAULT_INDEX = PlacesUtils.bookmarks.DEFAULT_INDEX;
function run_test() {
// folder to hold this test
var folderId =
PlacesUtils.bookmarks.createFolder(PlacesUtils.toolbarFolderId,
"", DEFAULT_INDEX);
// add a bookmark to the new folder
var bookmarkURI = uri("http://iasdjkf");
do_check_false(PlacesUtils.bookmarks.isBookmarked(bookmarkURI));
var bookmarkId = PlacesUtils.bookmarks.insertBookmark(folderId, bookmarkURI,
DEFAULT_INDEX, "");
do_check_eq(PlacesUtils.bookmarks.getItemTitle(bookmarkId), "");
// remove the folder using removeItem
PlacesUtils.bookmarks.removeItem(folderId);
do_check_eq(PlacesUtils.bookmarks.getBookmarkIdsForURI(bookmarkURI).length, 0);
do_check_false(PlacesUtils.bookmarks.isBookmarked(bookmarkURI));
do_check_eq(PlacesUtils.bookmarks.getItemIndex(bookmarkId), -1);
}

View file

@ -0,0 +1,209 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
// get bookmarks root id
var root = PlacesUtils.bookmarksMenuFolderId;
// a search term that matches a default bookmark
const searchTerm = "about";
var testRoot;
// main
function run_test() {
// create a folder to hold all the tests
// this makes the tests more tolerant of changes to the default bookmarks set
// also, name it using the search term, for testing that containers that match don't show up in query results
testRoot = PlacesUtils.bookmarks.createFolder(
root, searchTerm, PlacesUtils.bookmarks.DEFAULT_INDEX);
run_next_test();
}
add_test(function test_savedsearches_bookmarks() {
// add a bookmark that matches the search term
var bookmarkId = PlacesUtils.bookmarks.insertBookmark(
root, uri("http://foo.com"), PlacesUtils.bookmarks.DEFAULT_INDEX,
searchTerm);
// create a saved-search that matches a default bookmark
var searchId = PlacesUtils.bookmarks.insertBookmark(
testRoot, uri("place:terms=" + searchTerm + "&excludeQueries=1&expandQueries=1&queryType=1"),
PlacesUtils.bookmarks.DEFAULT_INDEX, searchTerm);
// query for the test root, expandQueries=0
// the query should show up as a regular bookmark
try {
let options = PlacesUtils.history.getNewQueryOptions();
options.expandQueries = 0;
let query = PlacesUtils.history.getNewQuery();
query.setFolders([testRoot], 1);
let result = PlacesUtils.history.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
let cc = rootNode.childCount;
do_check_eq(cc, 1);
for (let i = 0; i < cc; i++) {
let node = rootNode.getChild(i);
// test that queries have valid itemId
do_check_true(node.itemId > 0);
// test that the container is closed
node.QueryInterface(Ci.nsINavHistoryContainerResultNode);
do_check_eq(node.containerOpen, false);
}
rootNode.containerOpen = false;
}
catch (ex) {
do_throw("expandQueries=0 query error: " + ex);
}
// bookmark saved search
// query for the test root, expandQueries=1
// the query should show up as a query container, with 1 child
try {
let options = PlacesUtils.history.getNewQueryOptions();
options.expandQueries = 1;
let query = PlacesUtils.history.getNewQuery();
query.setFolders([testRoot], 1);
let result = PlacesUtils.history.executeQuery(query, options);
let rootNode = result.root;
rootNode.containerOpen = true;
let cc = rootNode.childCount;
do_check_eq(cc, 1);
for (let i = 0; i < cc; i++) {
let node = rootNode.getChild(i);
// test that query node type is container when expandQueries=1
do_check_eq(node.type, node.RESULT_TYPE_QUERY);
// test that queries (as containers) have valid itemId
do_check_true(node.itemId > 0);
node.QueryInterface(Ci.nsINavHistoryContainerResultNode);
node.containerOpen = true;
// test that queries have children when excludeItems=1
// test that query nodes don't show containers (shouldn't have our folder that matches)
// test that queries don't show themselves in query results (shouldn't have our saved search)
do_check_eq(node.childCount, 1);
// test that bookmark shows in query results
var item = node.getChild(0);
do_check_eq(item.itemId, bookmarkId);
// XXX - FAILING - test live-update of query results - add a bookmark that matches the query
// var tmpBmId = PlacesUtils.bookmarks.insertBookmark(
// root, uri("http://" + searchTerm + ".com"),
// PlacesUtils.bookmarks.DEFAULT_INDEX, searchTerm + "blah");
// do_check_eq(query.childCount, 2);
// XXX - test live-update of query results - delete a bookmark that matches the query
// PlacesUtils.bookmarks.removeItem(tmpBMId);
// do_check_eq(query.childCount, 1);
// test live-update of query results - add a folder that matches the query
PlacesUtils.bookmarks.createFolder(
root, searchTerm + "zaa", PlacesUtils.bookmarks.DEFAULT_INDEX);
do_check_eq(node.childCount, 1);
// test live-update of query results - add a query that matches the query
PlacesUtils.bookmarks.insertBookmark(
root, uri("place:terms=foo&excludeQueries=1&expandQueries=1&queryType=1"),
PlacesUtils.bookmarks.DEFAULT_INDEX, searchTerm + "blah");
do_check_eq(node.childCount, 1);
}
rootNode.containerOpen = false;
}
catch (ex) {
do_throw("expandQueries=1 bookmarks query: " + ex);
}
// delete the bookmark search
PlacesUtils.bookmarks.removeItem(searchId);
run_next_test();
});
add_task(function* test_savedsearches_history() {
// add a visit that matches the search term
var testURI = uri("http://" + searchTerm + ".com");
yield PlacesTestUtils.addVisits({ uri: testURI, title: searchTerm });
// create a saved-search that matches the visit we added
var searchId = PlacesUtils.bookmarks.insertBookmark(testRoot,
uri("place:terms=" + searchTerm + "&excludeQueries=1&expandQueries=1&queryType=0"),
PlacesUtils.bookmarks.DEFAULT_INDEX, searchTerm);
// query for the test root, expandQueries=1
// the query should show up as a query container, with 1 child
try {
var options = PlacesUtils.history.getNewQueryOptions();
options.expandQueries = 1;
var query = PlacesUtils.history.getNewQuery();
query.setFolders([testRoot], 1);
var result = PlacesUtils.history.executeQuery(query, options);
var rootNode = result.root;
rootNode.containerOpen = true;
var cc = rootNode.childCount;
do_check_eq(cc, 1);
for (var i = 0; i < cc; i++) {
var node = rootNode.getChild(i);
// test that query node type is container when expandQueries=1
do_check_eq(node.type, node.RESULT_TYPE_QUERY);
// test that queries (as containers) have valid itemId
do_check_eq(node.itemId, searchId);
node.QueryInterface(Ci.nsINavHistoryContainerResultNode);
node.containerOpen = true;
// test that queries have children when excludeItems=1
// test that query nodes don't show containers (shouldn't have our folder that matches)
// test that queries don't show themselves in query results (shouldn't have our saved search)
do_check_eq(node.childCount, 1);
// test that history visit shows in query results
var item = node.getChild(0);
do_check_eq(item.type, item.RESULT_TYPE_URI);
do_check_eq(item.itemId, -1); // history visit
do_check_eq(item.uri, testURI.spec); // history visit
// test live-update of query results - add a history visit that matches the query
yield PlacesTestUtils.addVisits({
uri: uri("http://foo.com"),
title: searchTerm + "blah"
});
do_check_eq(node.childCount, 2);
// test live-update of query results - delete a history visit that matches the query
PlacesUtils.history.removePage(uri("http://foo.com"));
do_check_eq(node.childCount, 1);
node.containerOpen = false;
}
// test live-update of moved queries
var tmpFolderId = PlacesUtils.bookmarks.createFolder(
testRoot, "foo", PlacesUtils.bookmarks.DEFAULT_INDEX);
PlacesUtils.bookmarks.moveItem(
searchId, tmpFolderId, PlacesUtils.bookmarks.DEFAULT_INDEX);
var tmpFolderNode = rootNode.getChild(0);
do_check_eq(tmpFolderNode.itemId, tmpFolderId);
tmpFolderNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
tmpFolderNode.containerOpen = true;
do_check_eq(tmpFolderNode.childCount, 1);
// test live-update of renamed queries
PlacesUtils.bookmarks.setItemTitle(searchId, "foo");
do_check_eq(tmpFolderNode.title, "foo");
// test live-update of deleted queries
PlacesUtils.bookmarks.removeItem(searchId);
try {
tmpFolderNode = root.getChild(1);
do_throw("query was not removed");
} catch (ex) {}
tmpFolderNode.containerOpen = false;
rootNode.containerOpen = false;
}
catch (ex) {
do_throw("expandQueries=1 bookmarks query: " + ex);
}
});

View file

@ -0,0 +1,50 @@
[DEFAULT]
head = head_bookmarks.js
tail =
skip-if = toolkit == 'android'
[test_1016953-renaming-uncompressed.js]
[test_1017502-bookmarks_foreign_count.js]
[test_384228.js]
[test_385829.js]
[test_388695.js]
[test_393498.js]
[test_395101.js]
[test_395593.js]
[test_405938_restore_queries.js]
[test_417228-exclude-from-backup.js]
[test_417228-other-roots.js]
[test_424958-json-quoted-folders.js]
[test_448584.js]
[test_458683.js]
[test_466303-json-remove-backups.js]
[test_477583_json-backup-in-future.js]
[test_675416.js]
[test_711914.js]
[test_818584-discard-duplicate-backups.js]
[test_818587_compress-bookmarks-backups.js]
[test_818593-store-backup-metadata.js]
[test_992901-backup-unsorted-hierarchy.js]
[test_997030-bookmarks-html-encode.js]
[test_1129529.js]
[test_async_observers.js]
[test_bmindex.js]
[test_bookmarkstree_cache.js]
[test_bookmarks.js]
[test_bookmarks_eraseEverything.js]
[test_bookmarks_fetch.js]
[test_bookmarks_getRecent.js]
[test_bookmarks_insert.js]
[test_bookmarks_notifications.js]
[test_bookmarks_remove.js]
[test_bookmarks_reorder.js]
[test_bookmarks_search.js]
[test_bookmarks_update.js]
[test_changeBookmarkURI.js]
[test_getBookmarkedURIFor.js]
[test_keywords.js]
[test_nsINavBookmarkObserver.js]
[test_protectRoots.js]
[test_removeFolderTransaction_reinsert.js]
[test_removeItem.js]
[test_savedsearches.js]

View file

@ -0,0 +1,8 @@
"use strict";
module.exports = {
"extends": [
"../../../../../testing/mochitest/browser.eslintrc.js",
"../../../../../testing/mochitest/mochitest.eslintrc.js"
]
};

View file

@ -0,0 +1,11 @@
<html>
<head>
<title>history.go(0)</title>
<script>
setTimeout('history.go(0)', 1000);
</script>
</head>
<body>
Testing history.go(0)
</body>
</html>

View file

@ -0,0 +1,8 @@
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="refresh" content="1">
<title>httprefresh</title>
</head><body>
Testing httprefresh
</body></html>

View file

@ -0,0 +1,11 @@
<html>
<head>
<title>location.reload()</title>
<script>
setTimeout('location.reload();', 100);
</script>
</head>
<body>
Testing location.reload();
</body>
</html>

View file

@ -0,0 +1,11 @@
<html>
<head>
<title>location.replace</title>
<script>
setTimeout('location.replace(window.location.href)', 1000);
</script>
</head>
<body>
Testing location.replace
</body>
</html>

View file

@ -0,0 +1,11 @@
<html>
<head>
<title>window.location.href</title>
<script>
setTimeout('window.location.href = window.location.href', 1000);
</script>
</head>
<body>
Testing window.location.href
</body>
</html>

View file

@ -0,0 +1,11 @@
<html>
<head>
<title>window.location</title>
<script>
setTimeout('window.location = window.location', 1000);
</script>
</head>
<body>
Testing window.location
</body>
</html>

View file

@ -0,0 +1,8 @@
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<iframe id="iframe"></iframe>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Link page 2</title>
<style type="text/css">
a:link { color: #0000ff; }
a:visited { color: #ff0000; }
</style>
</head>
<body>
<p><a href="461710_visited_page.html" id="link">Link to the second visited page</a></p>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Link page 3</title>
<style type="text/css">
a:link { color: #0000ff; }
a:visited { color: #ff0000; }
</style>
</head>
<body>
<p><a href="461710_visited_page.html" id="link">Link to the third visited page</a></p>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Link page</title>
<style type="text/css">
a:link { color: #0000ff; }
a:visited { color: #ff0000; }
</style>
</head>
<body>
<p><a href="461710_visited_page.html" id="link">Link to the visited page</a></p>
</body>
</html>

View file

@ -0,0 +1,9 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Visited page</title>
</head>
<body>
<p>This page is marked as visited</p>
</body>
</html>

View file

@ -0,0 +1,10 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<body>
<a id="clickme" href="redirect_twice.sjs">Redirect twice</a>
</body>
</html>

View file

@ -0,0 +1,26 @@
[DEFAULT]
support-files =
colorAnalyzer/category-discover.png
colorAnalyzer/dictionaryGeneric-16.png
colorAnalyzer/extensionGeneric-16.png
colorAnalyzer/localeGeneric.png
head.js
[browser_bug248970.js]
[browser_bug399606.js]
[browser_bug461710.js]
[browser_bug646422.js]
[browser_bug680727.js]
[browser_colorAnalyzer.js]
[browser_double_redirect.js]
[browser_favicon_privatebrowsing_perwindowpb.js]
[browser_favicon_setAndFetchFaviconForPage.js]
[browser_favicon_setAndFetchFaviconForPage_failures.js]
[browser_history_post.js]
[browser_notfound.js]
[browser_redirect.js]
[browser_settitle.js]
[browser_visited_notfound.js]
[browser_visituri.js]
[browser_visituri_nohistory.js]
[browser_visituri_privatebrowsing_perwindowpb.js]

View file

@ -0,0 +1,152 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
// This test performs checks on the history testing area as outlined
// https://wiki.mozilla.org/Firefox3.1/PrivateBrowsing/TestPlan#History
// http://developer.mozilla.org/en/Using_the_Places_history_service
var visitedURIs = [
"http://www.test-link.com/",
"http://www.test-typed.com/",
"http://www.test-bookmark.com/",
"http://www.test-redirect-permanent.com/",
"http://www.test-redirect-temporary.com/",
"http://www.test-embed.com/",
"http://www.test-framed.com/",
"http://www.test-download.com/"
].map(NetUtil.newURI.bind(NetUtil));
add_task(function* () {
let windowsToClose = [];
let placeItemsCount = 0;
registerCleanupFunction(function() {
windowsToClose.forEach(function(win) {
win.close();
});
});
yield PlacesTestUtils.clearHistory();
// Ensure we wait for the default bookmarks import.
yield new Promise(resolve => {
waitForCondition(() => {
placeItemsCount = getPlacesItemsCount();
return placeItemsCount > 0
}, resolve, "Should have default bookmarks")
});
// Create a handful of history items with various visit types
yield PlacesTestUtils.addVisits([
{ uri: visitedURIs[0], transition: TRANSITION_LINK },
{ uri: visitedURIs[1], transition: TRANSITION_TYPED },
{ uri: visitedURIs[2], transition: TRANSITION_BOOKMARK },
{ uri: visitedURIs[3], transition: TRANSITION_REDIRECT_PERMANENT },
{ uri: visitedURIs[4], transition: TRANSITION_REDIRECT_TEMPORARY },
{ uri: visitedURIs[5], transition: TRANSITION_EMBED },
{ uri: visitedURIs[6], transition: TRANSITION_FRAMED_LINK },
{ uri: visitedURIs[7], transition: TRANSITION_DOWNLOAD }
]);
placeItemsCount += 7;
// We added 7 new items to history.
is(getPlacesItemsCount(), placeItemsCount,
"Check the total items count");
function* testOnWindow(aIsPrivate, aCount) {
let win = yield new Promise(resolve => {
whenNewWindowLoaded({ private: aIsPrivate }, resolve);
});
windowsToClose.push(win);
// History items should be retrievable by query
yield checkHistoryItems();
// Updates the place items count
let count = getPlacesItemsCount();
// Create Bookmark
let title = "title " + windowsToClose.length;
let keyword = "keyword " + windowsToClose.length;
let url = "http://test-a-" + windowsToClose.length + ".com/";
yield PlacesUtils.bookmarks.insert({ url, title,
parentGuid: PlacesUtils.bookmarks.menuGuid });
yield PlacesUtils.keywords.insert({ url, keyword });
count++;
ok((yield PlacesUtils.bookmarks.fetch({ url })),
"Bookmark should be bookmarked, data should be retrievable");
is(getPlacesItemsCount(), count,
"Check the new bookmark items count");
is(isBookmarkAltered(), false, "Check if bookmark has been visited");
}
// Test on windows.
yield testOnWindow(false);
yield testOnWindow(true);
yield testOnWindow(false);
});
/**
* Function performs a really simple query on our places entries,
* and makes sure that the number of entries equal num_places_entries.
*/
function getPlacesItemsCount() {
// Get bookmarks count
let options = PlacesUtils.history.getNewQueryOptions();
options.includeHidden = true;
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
let root = PlacesUtils.history.executeQuery(
PlacesUtils.history.getNewQuery(), options).root;
root.containerOpen = true;
let cc = root.childCount;
root.containerOpen = false;
// Get history item count
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
root = PlacesUtils.history.executeQuery(
PlacesUtils.history.getNewQuery(), options).root;
root.containerOpen = true;
cc += root.childCount;
root.containerOpen = false;
return cc;
}
function* checkHistoryItems() {
for (let i = 0; i < visitedURIs.length; i++) {
let visitedUri = visitedURIs[i];
ok((yield promiseIsURIVisited(visitedUri)), "");
if (/embed/.test(visitedUri.spec)) {
is((yield PlacesTestUtils.isPageInDB(visitedUri)), false, "Check if URI is in database");
} else {
ok((yield PlacesTestUtils.isPageInDB(visitedUri)), "Check if URI is in database");
}
}
}
/**
* Function attempts to check if Bookmark-A has been visited
* during private browsing mode, function should return false
*
* @returns false if the accessCount has not changed
* true if the accessCount has changed
*/
function isBookmarkAltered() {
let options = PlacesUtils.history.getNewQueryOptions();
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
options.maxResults = 1; // should only expect a new bookmark
let query = PlacesUtils.history.getNewQuery();
query.setFolders([PlacesUtils.bookmarks.bookmarksMenuFolder], 1);
let root = PlacesUtils.history.executeQuery(query, options).root;
root.containerOpen = true;
is(root.childCount, options.maxResults, "Check new bookmarks results");
let node = root.getChild(0);
root.containerOpen = false;
return (node.accessCount != 0);
}

View file

@ -0,0 +1,77 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
gBrowser.selectedTab = gBrowser.addTab();
function test() {
waitForExplicitFinish();
var URIs = [
"http://example.com/tests/toolkit/components/places/tests/browser/399606-window.location.href.html",
"http://example.com/tests/toolkit/components/places/tests/browser/399606-history.go-0.html",
"http://example.com/tests/toolkit/components/places/tests/browser/399606-location.replace.html",
"http://example.com/tests/toolkit/components/places/tests/browser/399606-location.reload.html",
"http://example.com/tests/toolkit/components/places/tests/browser/399606-httprefresh.html",
"http://example.com/tests/toolkit/components/places/tests/browser/399606-window.location.html",
];
var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
getService(Ci.nsINavHistoryService);
// Create and add history observer.
var historyObserver = {
visitCount: Array(),
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onVisit: function (aURI, aVisitID, aTime, aSessionID, aReferringID,
aTransitionType) {
info("Received onVisit: " + aURI.spec);
if (aURI.spec in this.visitCount)
this.visitCount[aURI.spec]++;
else
this.visitCount[aURI.spec] = 1;
},
onTitleChanged: function () {},
onDeleteURI: function () {},
onClearHistory: function () {},
onPageChanged: function () {},
onDeleteVisits: function () {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
};
hs.addObserver(historyObserver, false);
function confirm_results() {
gBrowser.removeCurrentTab();
hs.removeObserver(historyObserver, false);
for (let aURI in historyObserver.visitCount) {
is(historyObserver.visitCount[aURI], 1,
"onVisit has been received right number of times for " + aURI);
}
PlacesTestUtils.clearHistory().then(finish);
}
var loadCount = 0;
function handleLoad(aEvent) {
loadCount++;
info("new load count is " + loadCount);
if (loadCount == 3) {
gBrowser.removeEventListener("DOMContentLoaded", handleLoad, true);
gBrowser.loadURI("about:blank");
executeSoon(check_next_uri);
}
}
function check_next_uri() {
if (URIs.length) {
let uri = URIs.shift();
loadCount = 0;
gBrowser.addEventListener("DOMContentLoaded", handleLoad, true);
gBrowser.loadURI(uri);
}
else {
confirm_results();
}
}
executeSoon(check_next_uri);
}

View file

@ -0,0 +1,82 @@
const kRed = "rgb(255, 0, 0)";
const kBlue = "rgb(0, 0, 255)";
const prefix = "http://example.com/tests/toolkit/components/places/tests/browser/461710_";
add_task(function* () {
let contentPage = prefix + "iframe.html";
let normalWindow = yield BrowserTestUtils.openNewBrowserWindow();
let browser = normalWindow.gBrowser.selectedBrowser;
BrowserTestUtils.loadURI(browser, contentPage);
yield BrowserTestUtils.browserLoaded(browser, contentPage);
let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
browser = privateWindow.gBrowser.selectedBrowser;
BrowserTestUtils.loadURI(browser, contentPage);
yield BrowserTestUtils.browserLoaded(browser, contentPage);
let tests = [{
win: normalWindow,
topic: "uri-visit-saved",
subtest: "visited_page.html"
}, {
win: normalWindow,
topic: "visited-status-resolution",
subtest: "link_page.html",
color: kRed,
message: "Visited link coloring should work outside of private mode"
}, {
win: privateWindow,
topic: "visited-status-resolution",
subtest: "link_page-2.html",
color: kBlue,
message: "Visited link coloring should not work inside of private mode"
}, {
win: normalWindow,
topic: "visited-status-resolution",
subtest: "link_page-3.html",
color: kRed,
message: "Visited link coloring should work outside of private mode"
}];
let visited_page_url = prefix + tests[0].subtest;
for (let test of tests) {
let promise = new Promise(resolve => {
let uri = NetUtil.newURI(visited_page_url);
Services.obs.addObserver(function observe(aSubject) {
if (uri.equals(aSubject.QueryInterface(Ci.nsIURI))) {
Services.obs.removeObserver(observe, test.topic);
resolve();
}
}, test.topic, false);
});
ContentTask.spawn(test.win.gBrowser.selectedBrowser, prefix + test.subtest, function* (aSrc) {
content.document.getElementById("iframe").src = aSrc;
});
yield promise;
if (test.color) {
// In e10s waiting for visited-status-resolution is not enough to ensure links
// have been updated, because it only tells us that messages to update links
// have been dispatched. We must still wait for the actual links to update.
yield BrowserTestUtils.waitForCondition(function* () {
let color = yield ContentTask.spawn(test.win.gBrowser.selectedBrowser, null, function* () {
let iframe = content.document.getElementById("iframe");
let elem = iframe.contentDocument.getElementById("link");
return content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.getVisitedDependentComputedStyle(elem, "", "color");
});
return (color == test.color);
}, test.message);
// The harness will consider the test as failed overall if there were no
// passes or failures, so record it as a pass.
ok(true, test.message);
}
}
yield BrowserTestUtils.closeWindow(normalWindow);
yield BrowserTestUtils.closeWindow(privateWindow);
});

View file

@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* Test for Bug 646224. Make sure that after changing the URI via
* history.pushState, the history service has a title stored for the new URI.
**/
add_task(function* () {
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, 'http://example.com');
let newTitlePromise = new Promise(resolve => {
let observer = {
onTitleChanged: function(uri, title) {
// If the uri of the page whose title is changing ends with 'new_page',
// then it's the result of our pushState.
if (/new_page$/.test(uri.spec)) {
resolve(title);
PlacesUtils.history.removeObserver(observer);
}
},
onBeginUpdateBatch: function() { },
onEndUpdateBatch: function() { },
onVisit: function() { },
onDeleteURI: function() { },
onClearHistory: function() { },
onPageChanged: function() { },
onDeleteVisits: function() { },
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
};
PlacesUtils.history.addObserver(observer, false);
});
yield ContentTask.spawn(tab.linkedBrowser, null, function* () {
let title = content.document.title;
content.history.pushState('', '', 'new_page');
Assert.ok(title, "Content window should initially have a title.");
});
let newtitle = yield newTitlePromise;
yield ContentTask.spawn(tab.linkedBrowser, { newtitle }, function* (args) {
Assert.equal(args.newtitle, content.document.title, "Title after pushstate.");
});
yield PlacesTestUtils.clearHistory();
gBrowser.removeTab(tab);
});

View file

@ -0,0 +1,109 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/* Ensure that clicking the button in the Offline mode neterror page updates
global history. See bug 680727. */
/* TEST_PATH=toolkit/components/places/tests/browser/browser_bug680727.js make -C $(OBJDIR) mochitest-browser-chrome */
const kUniqueURI = Services.io.newURI("http://mochi.test:8888/#bug_680727",
null, null);
var gAsyncHistory =
Cc["@mozilla.org/browser/history;1"].getService(Ci.mozIAsyncHistory);
var proxyPrefValue;
var ourTab;
function test() {
waitForExplicitFinish();
// Tests always connect to localhost, and per bug 87717, localhost is now
// reachable in offline mode. To avoid this, disable any proxy.
proxyPrefValue = Services.prefs.getIntPref("network.proxy.type");
Services.prefs.setIntPref("network.proxy.type", 0);
// Clear network cache.
Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
.getService(Components.interfaces.nsICacheStorageService)
.clear();
// Go offline, expecting the error page.
Services.io.offline = true;
BrowserTestUtils.openNewForegroundTab(gBrowser).then(tab => {
ourTab = tab;
BrowserTestUtils.waitForContentEvent(ourTab.linkedBrowser, "DOMContentLoaded")
.then(errorListener);
BrowserTestUtils.loadURI(ourTab.linkedBrowser, kUniqueURI.spec);
});
}
// ------------------------------------------------------------------------------
// listen to loading the neterror page. (offline mode)
function errorListener() {
ok(Services.io.offline, "Services.io.offline is true.");
// This is an error page.
ContentTask.spawn(ourTab.linkedBrowser, kUniqueURI.spec, function(uri) {
Assert.equal(content.document.documentURI.substring(0, 27),
"about:neterror?e=netOffline", "Document URI is the error page.");
// But location bar should show the original request.
Assert.equal(content.location.href, uri, "Docshell URI is the original URI.");
}).then(() => {
// Global history does not record URI of a failed request.
return PlacesTestUtils.promiseAsyncUpdates().then(() => {
gAsyncHistory.isURIVisited(kUniqueURI, errorAsyncListener);
});
});
}
function errorAsyncListener(aURI, aIsVisited) {
ok(kUniqueURI.equals(aURI) && !aIsVisited,
"The neterror page is not listed in global history.");
Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
// Now press the "Try Again" button, with offline mode off.
Services.io.offline = false;
BrowserTestUtils.waitForContentEvent(ourTab.linkedBrowser, "DOMContentLoaded")
.then(reloadListener);
ContentTask.spawn(ourTab.linkedBrowser, null, function() {
Assert.ok(content.document.getElementById("errorTryAgain"),
"The error page has got a #errorTryAgain element");
content.document.getElementById("errorTryAgain").click();
});
}
// ------------------------------------------------------------------------------
// listen to reload of neterror.
function reloadListener() {
// This listener catches "DOMContentLoaded" on being called
// nsIWPL::onLocationChange(...). That is right *AFTER*
// IHistory::VisitURI(...) is called.
ok(!Services.io.offline, "Services.io.offline is false.");
ContentTask.spawn(ourTab.linkedBrowser, kUniqueURI.spec, function(uri) {
// This is not an error page.
Assert.equal(content.document.documentURI, uri,
"Document URI is not the offline-error page, but the original URI.");
}).then(() => {
// Check if global history remembers the successfully-requested URI.
PlacesTestUtils.promiseAsyncUpdates().then(() => {
gAsyncHistory.isURIVisited(kUniqueURI, reloadAsyncListener);
});
});
}
function reloadAsyncListener(aURI, aIsVisited) {
ok(kUniqueURI.equals(aURI) && aIsVisited, "We have visited the URI.");
PlacesTestUtils.clearHistory().then(finish);
}
registerCleanupFunction(function* () {
Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
Services.io.offline = false;
yield BrowserTestUtils.removeTab(ourTab);
});

View file

@ -0,0 +1,259 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
"use strict";
Cu.import("resource://gre/modules/Services.jsm");
const CA = Cc["@mozilla.org/places/colorAnalyzer;1"].
getService(Ci.mozIColorAnalyzer);
const hiddenWindowDoc = Cc["@mozilla.org/appshell/appShellService;1"].
getService(Ci.nsIAppShellService).
hiddenDOMWindow.document;
const XHTML_NS = "http://www.w3.org/1999/xhtml";
/**
* Passes the given uri to findRepresentativeColor.
* If expected is null, you expect it to fail.
* If expected is a function, it will call that function.
* If expected is a color, you expect that color to be returned.
* Message is used in the calls to is().
*/
function frcTest(uri, expected, message) {
return new Promise(resolve => {
CA.findRepresentativeColor(Services.io.newURI(uri, "", null),
function(success, color) {
if (expected == null) {
ok(!success, message);
} else if (typeof expected == "function") {
expected(color, message);
} else {
ok(success, "success: " + message);
is(color, expected, message);
}
resolve();
});
});
}
/**
* Handy function for getting an image into findRepresentativeColor and testing it.
* Makes a canvas with the given dimensions, calls paintCanvasFunc with the 2d
* context of the canvas, sticks the generated canvas into findRepresentativeColor.
* See frcTest.
*/
function canvasTest(width, height, paintCanvasFunc, expected, message) {
let canvas = hiddenWindowDoc.createElementNS(XHTML_NS, "canvas");
canvas.width = width;
canvas.height = height;
paintCanvasFunc(canvas.getContext("2d"));
let uri = canvas.toDataURL();
return frcTest(uri, expected, message);
}
// simple test - draw a red box in the center, make sure we get red back
add_task(function* test_redSquare() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "red";
ctx.fillRect(2, 2, 12, 12);
}, 0xFF0000, "redSquare analysis returns red");
});
// draw a blue square in one corner, red in the other, such that blue overlaps
// red by one pixel, making it the dominant color
add_task(function* test_blueOverlappingRed() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 8, 8);
ctx.fillStyle = "blue";
ctx.fillRect(7, 7, 8, 8);
}, 0x0000FF, "blueOverlappingRed analysis returns blue");
});
// draw a red gradient next to a solid blue rectangle to ensure that a large
// block of similar colors beats out a smaller block of one color
add_task(function* test_redGradientBlueSolid() {
yield canvasTest(16, 16, function(ctx) {
let gradient = ctx.createLinearGradient(0, 0, 1, 15);
gradient.addColorStop(0, "#FF0000");
gradient.addColorStop(1, "#FF0808");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 16, 16);
ctx.fillStyle = "blue";
ctx.fillRect(9, 0, 7, 16);
}, function(actual, message) {
ok(actual >= 0xFF0000 && actual <= 0xFF0808, message);
}, "redGradientBlueSolid analysis returns redish");
});
// try a transparent image, should fail
add_task(function* test_transparent() {
yield canvasTest(16, 16, function(ctx) {
// do nothing!
}, null, "transparent analysis fails");
});
add_task(function* test_invalidURI() {
yield frcTest("data:blah,Imnotavaliddatauri", null, "invalid URI analysis fails");
});
add_task(function* test_malformedPNGURI() {
yield frcTest("data:image/png;base64,iVBORblahblahblah", null,
"malformed PNG URI analysis fails");
});
add_task(function* test_unresolvableURI() {
yield frcTest("http://www.example.com/blah/idontexist.png", null,
"unresolvable URI analysis fails");
});
// draw a small blue box on a red background to make sure the algorithm avoids
// using the background color
add_task(function* test_blueOnRedBackground() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 16, 16);
ctx.fillStyle = "blue";
ctx.fillRect(4, 4, 8, 8);
}, 0x0000FF, "blueOnRedBackground analysis returns blue");
});
// draw a slightly different color in the corners to make sure the corner colors
// don't have to be exactly equal to be considered the background color
add_task(function* test_variableBackground() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 16, 16);
ctx.fillStyle = "#FEFEFE";
ctx.fillRect(15, 0, 1, 1);
ctx.fillStyle = "#FDFDFD";
ctx.fillRect(15, 15, 1, 1);
ctx.fillStyle = "#FCFCFC";
ctx.fillRect(0, 15, 1, 1);
ctx.fillStyle = "black";
ctx.fillRect(4, 4, 8, 8);
}, 0x000000, "variableBackground analysis returns black");
});
// like the above test, but make the colors different enough that they aren't
// considered the background color
add_task(function* test_tooVariableBackground() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 16, 16);
ctx.fillStyle = "#EEDDCC";
ctx.fillRect(15, 0, 1, 1);
ctx.fillStyle = "#DDDDDD";
ctx.fillRect(15, 15, 1, 1);
ctx.fillStyle = "#CCCCCC";
ctx.fillRect(0, 15, 1, 1);
ctx.fillStyle = "black";
ctx.fillRect(4, 4, 8, 8);
}, function(actual, message) {
isnot(actual, 0x000000, message);
}, "tooVariableBackground analysis doesn't return black");
});
// draw a small black/white box over transparent background to make sure the
// algorithm doesn't think rgb(0,0,0) == rgba(0,0,0,0)
add_task(function* test_transparentBackgroundConflation() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "black";
ctx.fillRect(2, 2, 12, 12);
ctx.fillStyle = "white";
ctx.fillRect(5, 5, 6, 6);
}, 0x000000, "transparentBackgroundConflation analysis returns black");
});
// make sure we fall back to the background color if we have no other choice
// (instead of failing as if there were no colors)
add_task(function* test_backgroundFallback() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, 16, 16);
}, 0x000000, "backgroundFallback analysis returns black");
});
// draw red rectangle next to a pink one to make sure the algorithm picks the
// more interesting color
add_task(function* test_interestingColorPreference() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "#FFDDDD";
ctx.fillRect(0, 0, 16, 16);
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 3, 16);
}, 0xFF0000, "interestingColorPreference analysis returns red");
});
// draw high saturation but dark red next to slightly less saturated color but
// much lighter, to make sure the algorithm doesn't pick colors that are
// nearly black just because of high saturation (in HSL terms)
add_task(function* test_saturationDependence() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "hsl(0, 100%, 5%)";
ctx.fillRect(0, 0, 16, 16);
ctx.fillStyle = "hsl(0, 90%, 35%)";
ctx.fillRect(0, 0, 8, 16);
}, 0xA90808, "saturationDependence analysis returns lighter red");
});
// make sure the preference for interesting colors won't stupidly pick 1 pixel
// of red over 169 black pixels
add_task(function* test_interestingColorPreferenceLenient() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "black";
ctx.fillRect(1, 1, 13, 13);
ctx.fillStyle = "red";
ctx.fillRect(3, 3, 1, 1);
}, 0x000000, "interestingColorPreferenceLenient analysis returns black");
});
// ...but 6 pixels of red is more reasonable
add_task(function* test_interestingColorPreferenceNotTooLenient() {
yield canvasTest(16, 16, function(ctx) {
ctx.fillStyle = "black";
ctx.fillRect(1, 1, 13, 13);
ctx.fillStyle = "red";
ctx.fillRect(3, 3, 3, 2);
}, 0xFF0000, "interestingColorPreferenceNotTooLenient analysis returns red");
});
var maxPixels = 144; // see ColorAnalyzer MAXIMUM_PIXELS const
// make sure that images larger than maxPixels*maxPixels fail
add_task(function* test_imageTooLarge() {
yield canvasTest(1+maxPixels, 1+maxPixels, function(ctx) {
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 1+maxPixels, 1+maxPixels);
}, null, "imageTooLarge analysis fails");
});
// the rest of the tests are for coverage of "real" favicons
// exact color isn't terribly important, just make sure it's reasonable
const filePrefix = getRootDirectory(gTestPath) + "colorAnalyzer/";
add_task(function* test_categoryDiscover() {
yield frcTest(filePrefix + "category-discover.png", 0xB28D3A,
"category-discover analysis returns red");
});
add_task(function* test_localeGeneric() {
yield frcTest(filePrefix + "localeGeneric.png", 0x3EC23E,
"localeGeneric analysis returns green");
});
add_task(function* test_dictionaryGeneric() {
yield frcTest(filePrefix + "dictionaryGeneric-16.png", 0x854C30,
"dictionaryGeneric-16 analysis returns brown");
});
add_task(function* test_extensionGeneric() {
yield frcTest(filePrefix + "extensionGeneric-16.png", 0x53BA3F,
"extensionGeneric-16 analysis returns green");
});

View file

@ -0,0 +1,63 @@
// Test for bug 411966.
// When a page redirects multiple times, from_visit should point to the
// previous visit in the chain, not to the first visit in the chain.
add_task(function* () {
yield PlacesTestUtils.clearHistory();
const BASE_URL = "http://example.com/tests/toolkit/components/places/tests/browser/";
const TEST_URI = NetUtil.newURI(BASE_URL + "begin.html");
const FIRST_REDIRECTING_URI = NetUtil.newURI(BASE_URL + "redirect_twice.sjs");
const FINAL_URI = NetUtil.newURI(BASE_URL + "final.html");
let promiseVisits = new Promise(resolve => {
PlacesUtils.history.addObserver({
__proto__: NavHistoryObserver.prototype,
_notified: [],
onVisit: function (uri, id, time, sessionId, referrerId, transition) {
info("Received onVisit: " + uri.spec);
this._notified.push(uri);
if (!uri.equals(FINAL_URI)) {
return;
}
is(this._notified.length, 4);
PlacesUtils.history.removeObserver(this);
Task.spawn(function* () {
// Get all pages visited from the original typed one
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.execute(
`SELECT url FROM moz_historyvisits
JOIN moz_places h ON h.id = place_id
WHERE from_visit IN
(SELECT v.id FROM moz_historyvisits v
JOIN moz_places p ON p.id = v.place_id
WHERE p.url_hash = hash(:url) AND p.url = :url)
`, { url: TEST_URI.spec });
is(rows.length, 1, "Found right number of visits");
let visitedUrl = rows[0].getResultByName("url");
// Check that redirect from_visit is not from the original typed one
is(visitedUrl, FIRST_REDIRECTING_URI.spec, "Check referrer for " + visitedUrl);
resolve();
});
}
}, false);
});
PlacesUtils.history.markPageAsTyped(TEST_URI);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_URI.spec,
}, function* (browser) {
// Load begin page, click link on page to record visits.
yield BrowserTestUtils.synthesizeMouseAtCenter("#clickme", {}, browser);
yield promiseVisits;
});
yield PlacesTestUtils.clearHistory();
});

View file

@ -0,0 +1,43 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
function test() {
waitForExplicitFinish();
const pageURI =
"http://example.org/tests/toolkit/components/places/tests/browser/favicon.html";
let windowsToClose = [];
registerCleanupFunction(function() {
windowsToClose.forEach(function(aWin) {
aWin.close();
});
});
function testOnWindow(aIsPrivate, aCallback) {
whenNewWindowLoaded({private: aIsPrivate}, function(aWin) {
windowsToClose.push(aWin);
executeSoon(() => aCallback(aWin));
});
}
function waitForTabLoad(aWin, aCallback) {
aWin.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
aWin.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
aCallback();
}, true);
aWin.gBrowser.selectedBrowser.loadURI(pageURI);
}
testOnWindow(true, function(win) {
waitForTabLoad(win, function() {
PlacesUtils.favicons.getFaviconURLForPage(NetUtil.newURI(pageURI),
function(uri, dataLen, data, mimeType) {
is(uri, null, "No result should be found");
finish();
}
);
});
});
}

View file

@ -0,0 +1,152 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
// This file tests the normal operation of setAndFetchFaviconForPage.
function test() {
// Initialization
waitForExplicitFinish();
let windowsToClose = [];
let favIconLocation =
"http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png";
let favIconURI = NetUtil.newURI(favIconLocation);
let favIconMimeType= "image/png";
let pageURI;
let favIconData;
function testOnWindow(aOptions, aCallback) {
whenNewWindowLoaded(aOptions, function(aWin) {
windowsToClose.push(aWin);
executeSoon(() => aCallback(aWin));
});
}
// This function is called after calling finish() on the test.
registerCleanupFunction(function() {
windowsToClose.forEach(function(aWin) {
aWin.close();
});
});
function getIconFile(aCallback) {
NetUtil.asyncFetch({
uri: favIconLocation,
loadUsingSystemPrincipal: true,
contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON
}, function(inputStream, status) {
if (!Components.isSuccessCode(status)) {
ok(false, "Could not get the icon file");
// Handle error.
return;
}
// Check the returned size versus the expected size.
let size = inputStream.available();
favIconData = NetUtil.readInputStreamToString(inputStream, size);
is(size, favIconData.length, "Check correct icon size");
// Check that the favicon loaded correctly before starting the actual tests.
is(favIconData.length, 344, "Check correct icon length (344)");
if (aCallback) {
aCallback();
} else {
finish();
}
});
}
function testNormal(aWindow, aCallback) {
pageURI = NetUtil.newURI("http://example.com/normal");
waitForFaviconChanged(pageURI, favIconURI, aWindow,
function testNormalCallback() {
checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow,
aCallback);
}
);
addVisits({uri: pageURI, transition: TRANSITION_TYPED}, aWindow,
function () {
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI,
true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
}
);
}
function testAboutURIBookmarked(aWindow, aCallback) {
pageURI = NetUtil.newURI("about:testAboutURI_bookmarked");
waitForFaviconChanged(pageURI, favIconURI, aWindow,
function testAboutURIBookmarkedCallback() {
checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow,
aCallback);
}
);
aWindow.PlacesUtils.bookmarks.insertBookmark(
aWindow.PlacesUtils.unfiledBookmarksFolderId, pageURI,
aWindow.PlacesUtils.bookmarks.DEFAULT_INDEX, pageURI.spec);
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI,
true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
}
function testPrivateBrowsingBookmarked(aWindow, aCallback) {
pageURI = NetUtil.newURI("http://example.com/privateBrowsing_bookmarked");
waitForFaviconChanged(pageURI, favIconURI, aWindow,
function testPrivateBrowsingBookmarkedCallback() {
checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow,
aCallback);
}
);
aWindow.PlacesUtils.bookmarks.insertBookmark(
aWindow.PlacesUtils.unfiledBookmarksFolderId, pageURI,
aWindow.PlacesUtils.bookmarks.DEFAULT_INDEX, pageURI.spec);
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI,
true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
}
function testDisabledHistoryBookmarked(aWindow, aCallback) {
pageURI = NetUtil.newURI("http://example.com/disabledHistory_bookmarked");
waitForFaviconChanged(pageURI, favIconURI, aWindow,
function testDisabledHistoryBookmarkedCallback() {
checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow,
aCallback);
}
);
// Disable history while changing the favicon.
aWindow.Services.prefs.setBoolPref("places.history.enabled", false);
aWindow.PlacesUtils.bookmarks.insertBookmark(
aWindow.PlacesUtils.unfiledBookmarksFolderId, pageURI,
aWindow.PlacesUtils.bookmarks.DEFAULT_INDEX, pageURI.spec);
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI,
true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
// The setAndFetchFaviconForPage function calls CanAddURI synchronously, thus
// we can set the preference back to true immediately. We don't clear the
// preference because not all products enable Places by default.
aWindow.Services.prefs.setBoolPref("places.history.enabled", true);
}
getIconFile(function () {
testOnWindow({}, function(aWin) {
testNormal(aWin, function () {
testOnWindow({}, function(aWin2) {
testAboutURIBookmarked(aWin2, function () {
testOnWindow({private: true}, function(aWin3) {
testPrivateBrowsingBookmarked(aWin3, function () {
testOnWindow({}, function(aWin4) {
testDisabledHistoryBookmarked(aWin4, finish);
});
});
});
});
});
});
});
});
}

View file

@ -0,0 +1,261 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
/**
* This file tests setAndFetchFaviconForPage when it is called with invalid
* arguments, and when no favicon is stored for the given arguments.
*/
function test() {
// Initialization
waitForExplicitFinish();
let windowsToClose = [];
let favIcon16Location =
"http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal16.png";
let favIcon32Location =
"http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png";
let favIcon16URI = NetUtil.newURI(favIcon16Location);
let favIcon32URI = NetUtil.newURI(favIcon32Location);
let lastPageURI = NetUtil.newURI("http://example.com/verification");
// This error icon must stay in sync with FAVICON_ERRORPAGE_URL in
// nsIFaviconService.idl, aboutCertError.xhtml and netError.xhtml.
let favIconErrorPageURI =
NetUtil.newURI("chrome://global/skin/icons/warning-16.png");
let favIconsResultCount = 0;
function testOnWindow(aOptions, aCallback) {
whenNewWindowLoaded(aOptions, function(aWin) {
windowsToClose.push(aWin);
executeSoon(() => aCallback(aWin));
});
}
// This function is called after calling finish() on the test.
registerCleanupFunction(function() {
windowsToClose.forEach(function(aWin) {
aWin.close();
});
});
function checkFavIconsDBCount(aCallback) {
let stmt = DBConn().createAsyncStatement("SELECT url FROM moz_favicons");
stmt.executeAsync({
handleResult: function final_handleResult(aResultSet) {
while (aResultSet.getNextRow()) {
favIconsResultCount++;
}
},
handleError: function final_handleError(aError) {
throw ("Unexpected error (" + aError.result + "): " + aError.message);
},
handleCompletion: function final_handleCompletion(aReason) {
// begin testing
info("Previous records in moz_favicons: " + favIconsResultCount);
if (aCallback) {
aCallback();
}
}
});
stmt.finalize();
}
function testNullPageURI(aWindow, aCallback) {
try {
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(null, favIcon16URI,
true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
throw ("Exception expected because aPageURI is null.");
} catch (ex) {
// We expected an exception.
ok(true, "Exception expected because aPageURI is null");
}
if (aCallback) {
aCallback();
}
}
function testNullFavIconURI(aWindow, aCallback) {
try {
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(
NetUtil.newURI("http://example.com/null_faviconURI"), null,
true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null, Services.scriptSecurityManager.getSystemPrincipal());
throw ("Exception expected because aFaviconURI is null.");
} catch (ex) {
// We expected an exception.
ok(true, "Exception expected because aFaviconURI is null.");
}
if (aCallback) {
aCallback();
}
}
function testAboutURI(aWindow, aCallback) {
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(
NetUtil.newURI("about:testAboutURI"), favIcon16URI,
true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
null, Services.scriptSecurityManager.getSystemPrincipal());
if (aCallback) {
aCallback();
}
}
function testPrivateBrowsingNonBookmarkedURI(aWindow, aCallback) {
let pageURI = NetUtil.newURI("http://example.com/privateBrowsing");
addVisits({ uri: pageURI, transitionType: TRANSITION_TYPED }, aWindow,
function () {
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
favIcon16URI, true,
aWindow.PlacesUtils.favicons.FAVICON_LOAD_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
if (aCallback) {
aCallback();
}
});
}
function testDisabledHistory(aWindow, aCallback) {
let pageURI = NetUtil.newURI("http://example.com/disabledHistory");
addVisits({ uri: pageURI, transition: TRANSITION_TYPED }, aWindow,
function () {
aWindow.Services.prefs.setBoolPref("places.history.enabled", false);
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
favIcon16URI, true,
aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
// The setAndFetchFaviconForPage function calls CanAddURI synchronously, thus
// we can set the preference back to true immediately . We don't clear the
// preference because not all products enable Places by default.
aWindow.Services.prefs.setBoolPref("places.history.enabled", true);
if (aCallback) {
aCallback();
}
});
}
function testErrorIcon(aWindow, aCallback) {
let pageURI = NetUtil.newURI("http://example.com/errorIcon");
addVisits({ uri: pageURI, transition: TRANSITION_TYPED }, aWindow,
function () {
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
favIconErrorPageURI, true,
aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
if (aCallback) {
aCallback();
}
});
}
function testNonExistingPage(aWindow, aCallback) {
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(
NetUtil.newURI("http://example.com/nonexistingPage"), favIcon16URI,
true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
if (aCallback) {
aCallback();
}
}
function testFinalVerification(aWindow, aCallback) {
// Only the last test should raise the onPageChanged notification,
// executing the waitForFaviconChanged callback.
waitForFaviconChanged(lastPageURI, favIcon32URI, aWindow,
function final_callback() {
// Check that only one record corresponding to the last favicon is present.
let resultCount = 0;
let stmt = DBConn().createAsyncStatement("SELECT url FROM moz_favicons");
stmt.executeAsync({
handleResult: function final_handleResult(aResultSet) {
// If the moz_favicons DB had been previously loaded (before our
// test began), we should focus only in the URI we are testing and
// skip the URIs not related to our test.
if (favIconsResultCount > 0) {
for (let row; (row = aResultSet.getNextRow()); ) {
if (favIcon32URI.spec === row.getResultByIndex(0)) {
is(favIcon32URI.spec, row.getResultByIndex(0),
"Check equal favicons");
resultCount++;
}
}
} else {
for (let row; (row = aResultSet.getNextRow()); ) {
is(favIcon32URI.spec, row.getResultByIndex(0),
"Check equal favicons");
resultCount++;
}
}
},
handleError: function final_handleError(aError) {
throw ("Unexpected error (" + aError.result + "): " + aError.message);
},
handleCompletion: function final_handleCompletion(aReason) {
is(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason,
"Check reasons are equal");
is(1, resultCount, "Check result count");
if (aCallback) {
aCallback();
}
}
});
stmt.finalize();
});
// This is the only test that should cause the waitForFaviconChanged
// callback to be invoked. In turn, the callback will invoke
// finish() causing the tests to finish.
addVisits({ uri: lastPageURI, transition: TRANSITION_TYPED }, aWindow,
function () {
aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(lastPageURI,
favIcon32URI, true,
aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
Services.scriptSecurityManager.getSystemPrincipal());
});
}
checkFavIconsDBCount(function () {
testOnWindow({}, function(aWin) {
testNullPageURI(aWin, function () {
testOnWindow({}, function(aWin2) {
testNullFavIconURI(aWin2, function() {
testOnWindow({}, function(aWin3) {
testAboutURI(aWin3, function() {
testOnWindow({private: true}, function(aWin4) {
testPrivateBrowsingNonBookmarkedURI(aWin4, function () {
testOnWindow({}, function(aWin5) {
testDisabledHistory(aWin5, function () {
testOnWindow({}, function(aWin6) {
testErrorIcon(aWin6, function() {
testOnWindow({}, function(aWin7) {
testNonExistingPage(aWin7, function() {
testOnWindow({}, function(aWin8) {
testFinalVerification(aWin8, function() {
finish();
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
});
}

View file

@ -0,0 +1,23 @@
const PAGE_URI = "http://example.com/tests/toolkit/components/places/tests/browser/history_post.html";
const SJS_URI = NetUtil.newURI("http://example.com/tests/toolkit/components/places/tests/browser/history_post.sjs");
add_task(function* () {
yield BrowserTestUtils.withNewTab({gBrowser, url: PAGE_URI}, Task.async(function* (aBrowser) {
yield ContentTask.spawn(aBrowser, null, function* () {
let doc = content.document;
let submit = doc.getElementById("submit");
let iframe = doc.getElementById("post_iframe");
let p = new Promise((resolve, reject) => {
iframe.addEventListener("load", function onLoad() {
iframe.removeEventListener("load", onLoad);
resolve();
});
});
submit.click();
yield p;
});
let visited = yield promiseIsURIVisited(SJS_URI);
ok(!visited, "The POST page should not be added to history");
ok(!(yield PlacesTestUtils.isPageInDB(SJS_URI.spec)), "The page should not be in the database");
}));
});

View file

@ -0,0 +1,46 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
add_task(function* () {
const TEST_URL = "http://mochi.test:8888/notFoundPage.html";
// Used to verify errors are not marked as typed.
PlacesUtils.history.markPageAsTyped(NetUtil.newURI(TEST_URL));
// Create and add history observer.
let visitedPromise = new Promise(resolve => {
let historyObserver = {
onVisit: function (aURI, aVisitID, aTime, aSessionID, aReferringID,
aTransitionType) {
PlacesUtils.history.removeObserver(historyObserver);
info("Received onVisit: " + aURI.spec);
fieldForUrl(aURI, "frecency", function (aFrecency) {
is(aFrecency, 0, "Frecency should be 0");
fieldForUrl(aURI, "hidden", function (aHidden) {
is(aHidden, 0, "Page should not be hidden");
fieldForUrl(aURI, "typed", function (aTyped) {
is(aTyped, 0, "page should not be marked as typed");
resolve();
});
});
});
},
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onTitleChanged: function () {},
onDeleteURI: function () {},
onClearHistory: function () {},
onPageChanged: function () {},
onDeleteVisits: function () {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
};
PlacesUtils.history.addObserver(historyObserver, false);
});
let newTabPromise = BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
yield Promise.all([visitedPromise, newTabPromise]);
yield PlacesTestUtils.clearHistory();
gBrowser.removeCurrentTab();
});

View file

@ -0,0 +1,61 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
add_task(function* () {
const REDIRECT_URI = NetUtil.newURI("http://mochi.test:8888/tests/toolkit/components/places/tests/browser/redirect.sjs");
const TARGET_URI = NetUtil.newURI("http://mochi.test:8888/tests/toolkit/components/places/tests/browser/redirect-target.html");
// Create and add history observer.
let visitedPromise = new Promise(resolve => {
let historyObserver = {
_redirectNotified: false,
onVisit: function (aURI, aVisitID, aTime, aSessionID, aReferringID,
aTransitionType) {
info("Received onVisit: " + aURI.spec);
if (aURI.equals(REDIRECT_URI)) {
this._redirectNotified = true;
// Wait for the target page notification.
return;
}
PlacesUtils.history.removeObserver(historyObserver);
ok(this._redirectNotified, "The redirect should have been notified");
fieldForUrl(REDIRECT_URI, "frecency", function (aFrecency) {
ok(aFrecency != 0, "Frecency or the redirecting page should not be 0");
fieldForUrl(REDIRECT_URI, "hidden", function (aHidden) {
is(aHidden, 1, "The redirecting page should be hidden");
fieldForUrl(TARGET_URI, "frecency", function (aFrecency2) {
ok(aFrecency2 != 0, "Frecency of the target page should not be 0");
fieldForUrl(TARGET_URI, "hidden", function (aHidden2) {
is(aHidden2, 0, "The target page should not be hidden");
resolve();
});
});
});
});
},
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onTitleChanged: function () {},
onDeleteURI: function () {},
onClearHistory: function () {},
onPageChanged: function () {},
onDeleteVisits: function () {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
};
PlacesUtils.history.addObserver(historyObserver, false);
});
let newTabPromise = BrowserTestUtils.openNewForegroundTab(gBrowser, REDIRECT_URI.spec);
yield Promise.all([visitedPromise, newTabPromise]);
yield PlacesTestUtils.clearHistory();
gBrowser.removeCurrentTab();
});

View file

@ -0,0 +1,76 @@
var conn = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
/**
* Gets a single column value from either the places or historyvisits table.
*/
function getColumn(table, column, url)
{
var stmt = conn.createStatement(
`SELECT ${column} FROM ${table} WHERE url_hash = hash(:val) AND url = :val`);
try {
stmt.params.val = url;
stmt.executeStep();
return stmt.row[column];
}
finally {
stmt.finalize();
}
}
add_task(function* ()
{
// Make sure titles are correctly saved for a URI with the proper
// notifications.
// Create and add history observer.
let titleChangedPromise = new Promise(resolve => {
var historyObserver = {
data: [],
onBeginUpdateBatch: function() {},
onEndUpdateBatch: function() {},
onVisit: function(aURI, aVisitID, aTime, aSessionID, aReferringID,
aTransitionType) {
},
onTitleChanged: function(aURI, aPageTitle, aGUID) {
this.data.push({ uri: aURI, title: aPageTitle, guid: aGUID });
// We only expect one title change.
//
// Although we are loading two different pages, the first page does not
// have a title. Since the title starts out as empty and then is set
// to empty, there is no title change notification.
PlacesUtils.history.removeObserver(this);
resolve(this.data);
},
onDeleteURI: function() {},
onClearHistory: function() {},
onPageChanged: function() {},
onDeleteVisits: function() {},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver])
};
PlacesUtils.history.addObserver(historyObserver, false);
});
const url1 = "http://example.com/tests/toolkit/components/places/tests/browser/title1.html";
yield BrowserTestUtils.openNewForegroundTab(gBrowser, url1);
const url2 = "http://example.com/tests/toolkit/components/places/tests/browser/title2.html";
let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url2);
yield loadPromise;
let data = yield titleChangedPromise;
is(data[0].uri.spec, "http://example.com/tests/toolkit/components/places/tests/browser/title2.html");
is(data[0].title, "Some title");
is(data[0].guid, getColumn("moz_places", "guid", data[0].uri.spec));
data.forEach(function(item) {
var title = getColumn("moz_places", "title", data[0].uri.spec);
is(title, item.title);
});
gBrowser.removeCurrentTab();
yield PlacesTestUtils.clearHistory();
});

View file

@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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 TEST_URI = NetUtil.newURI("http://mochi.test:8888/notFoundPage.html");
function test() {
waitForExplicitFinish();
gBrowser.selectedTab = gBrowser.addTab();
registerCleanupFunction(function() {
gBrowser.removeCurrentTab();
});
// First add a visit to the page, this will ensure that later we skip
// updating the frecency for a newly not-found page.
addVisits({ uri: TEST_URI }, window, () => {
info("Added visit");
fieldForUrl(TEST_URI, "frecency", aFrecency => {
ok(aFrecency > 0, "Frecency should be > 0");
continueTest(aFrecency);
});
});
}
function continueTest(aOldFrecency) {
// Used to verify errors are not marked as typed.
PlacesUtils.history.markPageAsTyped(TEST_URI);
gBrowser.selectedBrowser.loadURI(TEST_URI.spec);
// Create and add history observer.
let historyObserver = {
__proto__: NavHistoryObserver.prototype,
onVisit: function (aURI, aVisitID, aTime, aSessionID, aReferringID,
aTransitionType) {
PlacesUtils.history.removeObserver(historyObserver);
info("Received onVisit: " + aURI.spec);
fieldForUrl(aURI, "frecency", function (aFrecency) {
is(aFrecency, aOldFrecency, "Frecency should be unchanged");
fieldForUrl(aURI, "hidden", function (aHidden) {
is(aHidden, 0, "Page should not be hidden");
fieldForUrl(aURI, "typed", function (aTyped) {
is(aTyped, 0, "page should not be marked as typed");
PlacesTestUtils.clearHistory().then(finish);
});
});
});
}
};
PlacesUtils.history.addObserver(historyObserver, false);
}

View file

@ -0,0 +1,84 @@
/**
* One-time observer callback.
*/
function promiseObserve(name, checkFn) {
return new Promise(resolve => {
Services.obs.addObserver(function observer(subject) {
if (checkFn(subject)) {
Services.obs.removeObserver(observer, name);
resolve();
}
}, name, false);
});
}
var conn = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
/**
* Gets a single column value from either the places or historyvisits table.
*/
function getColumn(table, column, fromColumnName, fromColumnValue) {
let sql = `SELECT ${column}
FROM ${table}
WHERE ${fromColumnName} = :val
${fromColumnName == "url" ? "AND url_hash = hash(:val)" : ""}
LIMIT 1`;
let stmt = conn.createStatement(sql);
try {
stmt.params.val = fromColumnValue;
ok(stmt.executeStep(), "Expect to get a row");
return stmt.row[column];
}
finally {
stmt.reset();
}
}
add_task(function* () {
// Make sure places visit chains are saved correctly with a redirect
// transitions.
// Part 1: observe history events that fire when a visit occurs.
// Make sure visits appear in order, and that the visit chain is correct.
var expectedUrls = [
"http://example.com/tests/toolkit/components/places/tests/browser/begin.html",
"http://example.com/tests/toolkit/components/places/tests/browser/redirect_twice.sjs",
"http://example.com/tests/toolkit/components/places/tests/browser/redirect_once.sjs",
"http://example.com/tests/toolkit/components/places/tests/browser/final.html"
];
var currentIndex = 0;
function checkObserver(subject) {
var uri = subject.QueryInterface(Ci.nsIURI);
var expected = expectedUrls[currentIndex];
is(uri.spec, expected, "Saved URL visit " + uri.spec);
var placeId = getColumn("moz_places", "id", "url", uri.spec);
var fromVisitId = getColumn("moz_historyvisits", "from_visit", "place_id", placeId);
if (currentIndex == 0) {
is(fromVisitId, 0, "First visit has no from visit");
}
else {
var lastVisitId = getColumn("moz_historyvisits", "place_id", "id", fromVisitId);
var fromVisitUrl = getColumn("moz_places", "url", "id", lastVisitId);
is(fromVisitUrl, expectedUrls[currentIndex - 1],
"From visit was " + expectedUrls[currentIndex - 1]);
}
currentIndex++;
return (currentIndex >= expectedUrls.length);
}
let visitUriPromise = promiseObserve("uri-visit-saved", checkObserver);
const testUrl = "http://example.com/tests/toolkit/components/places/tests/browser/begin.html";
yield BrowserTestUtils.openNewForegroundTab(gBrowser, testUrl);
// Load begin page, click link on page to record visits.
yield BrowserTestUtils.synthesizeMouseAtCenter("#clickme", { }, gBrowser.selectedBrowser);
yield visitUriPromise;
yield PlacesTestUtils.clearHistory();
gBrowser.removeCurrentTab();
});

View file

@ -0,0 +1,42 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
const INITIAL_URL = "http://example.com/tests/toolkit/components/places/tests/browser/begin.html";
const FINAL_URL = "http://example.com/tests/toolkit/components/places/tests/browser/final.html";
/**
* One-time observer callback.
*/
function promiseObserve(name)
{
return new Promise(resolve => {
Services.obs.addObserver(function observer(subject) {
Services.obs.removeObserver(observer, name);
resolve(subject);
}, name, false);
});
}
add_task(function* ()
{
yield new Promise(resolve => SpecialPowers.pushPrefEnv({"set": [["places.history.enabled", false]]}, resolve));
let visitUriPromise = promiseObserve("uri-visit-saved");
yield BrowserTestUtils.openNewForegroundTab(gBrowser, INITIAL_URL);
yield new Promise(resolve => SpecialPowers.popPrefEnv(resolve));
let browserLoadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
gBrowser.loadURI(FINAL_URL);
yield browserLoadedPromise;
let subject = yield visitUriPromise;
let uri = subject.QueryInterface(Ci.nsIURI);
is(uri.spec, FINAL_URL, "received expected visit");
yield PlacesTestUtils.clearHistory();
gBrowser.removeCurrentTab();
});

View file

@ -0,0 +1,73 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
function test() {
// initialization
waitForExplicitFinish();
let windowsToClose = [];
let initialURL =
"http://example.com/tests/toolkit/components/places/tests/browser/begin.html";
let finalURL =
"http://example.com/tests/toolkit/components/places/tests/browser/final.html";
let observer = null;
let enumerator = null;
let currentObserver = null;
let uri = null;
function doTest(aIsPrivateMode, aWindow, aTestURI, aCallback) {
observer = {
observe: function(aSubject, aTopic, aData) {
// The uri-visit-saved topic should only work when on normal mode.
if (aTopic == "uri-visit-saved") {
// Remove the observers set on per window private mode and normal
// mode.
enumerator = aWindow.Services.obs.enumerateObservers("uri-visit-saved");
while (enumerator.hasMoreElements()) {
currentObserver = enumerator.getNext();
aWindow.Services.obs.removeObserver(currentObserver, "uri-visit-saved");
}
// The expected visit should be the finalURL because private mode
// should not register a visit with the initialURL.
uri = aSubject.QueryInterface(Ci.nsIURI);
is(uri.spec, finalURL, "Check received expected visit");
}
}
};
aWindow.Services.obs.addObserver(observer, "uri-visit-saved", false);
BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then(aCallback);
aWindow.gBrowser.selectedBrowser.loadURI(aTestURI);
}
function testOnWindow(aOptions, aCallback) {
whenNewWindowLoaded(aOptions, function(aWin) {
windowsToClose.push(aWin);
// execute should only be called when need, like when you are opening
// web pages on the test. If calling executeSoon() is not necesary, then
// call whenNewWindowLoaded() instead of testOnWindow() on your test.
executeSoon(() => aCallback(aWin));
});
}
// This function is called after calling finish() on the test.
registerCleanupFunction(function() {
windowsToClose.forEach(function(aWin) {
aWin.close();
});
});
// test first when on private mode
testOnWindow({private: true}, function(aWin) {
doTest(true, aWin, initialURL, function() {
// then test when not on private mode
testOnWindow({}, function(aWin2) {
doTest(false, aWin2, finalURL, function () {
PlacesTestUtils.clearHistory().then(finish);
});
});
});
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

View file

@ -0,0 +1,13 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<link rel="shortcut icon" href="http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png">
</head>
<body>
OK we're done!
</body>
</html>

View file

@ -0,0 +1,10 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<body>
OK we're done!
</body>
</html>

View file

@ -0,0 +1,319 @@
Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
"resource://testing-common/PlacesTestUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
"resource://testing-common/BrowserTestUtils.jsm");
const TRANSITION_LINK = PlacesUtils.history.TRANSITION_LINK;
const TRANSITION_TYPED = PlacesUtils.history.TRANSITION_TYPED;
const TRANSITION_BOOKMARK = PlacesUtils.history.TRANSITION_BOOKMARK;
const TRANSITION_REDIRECT_PERMANENT = PlacesUtils.history.TRANSITION_REDIRECT_PERMANENT;
const TRANSITION_REDIRECT_TEMPORARY = PlacesUtils.history.TRANSITION_REDIRECT_TEMPORARY;
const TRANSITION_EMBED = PlacesUtils.history.TRANSITION_EMBED;
const TRANSITION_FRAMED_LINK = PlacesUtils.history.TRANSITION_FRAMED_LINK;
const TRANSITION_DOWNLOAD = PlacesUtils.history.TRANSITION_DOWNLOAD;
/**
* Returns a moz_places field value for a url.
*
* @param aURI
* The URI or spec to get field for.
* param aCallback
* Callback function that will get the property value.
*/
function fieldForUrl(aURI, aFieldName, aCallback)
{
let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
let stmt = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.DBConnection.createAsyncStatement(
`SELECT ${aFieldName} FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url`
);
stmt.params.page_url = url;
stmt.executeAsync({
_value: -1,
handleResult: function(aResultSet) {
let row = aResultSet.getNextRow();
if (!row)
ok(false, "The page should exist in the database");
this._value = row.getResultByName(aFieldName);
},
handleError: function() {},
handleCompletion: function(aReason) {
if (aReason != Ci.mozIStorageStatementCallback.REASON_FINISHED)
ok(false, "The statement should properly succeed");
aCallback(this._value);
}
});
stmt.finalize();
}
/**
* Generic nsINavHistoryObserver that doesn't implement anything, but provides
* dummy methods to prevent errors about an object not having a certain method.
*/
function NavHistoryObserver() {}
NavHistoryObserver.prototype = {
onBeginUpdateBatch: function () {},
onEndUpdateBatch: function () {},
onVisit: function () {},
onTitleChanged: function () {},
onDeleteURI: function () {},
onClearHistory: function () {},
onPageChanged: function () {},
onDeleteVisits: function () {},
QueryInterface: XPCOMUtils.generateQI([
Ci.nsINavHistoryObserver,
])
};
/**
* Waits for the first OnPageChanged notification for ATTRIBUTE_FAVICON, and
* verifies that it matches the expected page URI and associated favicon URI.
*
* This function also double-checks the GUID parameter of the notification.
*
* @param aExpectedPageURI
* nsIURI object of the page whose favicon should change.
* @param aExpectedFaviconURI
* nsIURI object of the newly associated favicon.
* @param aCallback
* This function is called after the check finished.
*/
function waitForFaviconChanged(aExpectedPageURI, aExpectedFaviconURI, aWindow,
aCallback) {
let historyObserver = {
__proto__: NavHistoryObserver.prototype,
onPageChanged: function WFFC_onPageChanged(aURI, aWhat, aValue, aGUID) {
if (aWhat != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
return;
}
aWindow.PlacesUtils.history.removeObserver(this);
ok(aURI.equals(aExpectedPageURI),
"Check URIs are equal for the page which favicon changed");
is(aValue, aExpectedFaviconURI.spec,
"Check changed favicon URI is the expected");
checkGuidForURI(aURI, aGUID);
if (aCallback) {
aCallback();
}
}
};
aWindow.PlacesUtils.history.addObserver(historyObserver, false);
}
/**
* Asynchronously adds visits to a page, invoking a callback function when done.
*
* @param aPlaceInfo
* Either an nsIURI, in such a case a single LINK visit will be added.
* Or can be an object describing the visit to add, or an array
* of these objects:
* { uri: nsIURI of the page,
* transition: one of the TRANSITION_* from nsINavHistoryService,
* [optional] title: title of the page,
* [optional] visitDate: visit date in microseconds from the epoch
* [optional] referrer: nsIURI of the referrer for this visit
* }
* @param [optional] aCallback
* Function to be invoked on completion.
* @param [optional] aStack
* The stack frame used to report errors.
*/
function addVisits(aPlaceInfo, aWindow, aCallback, aStack) {
let places = [];
if (aPlaceInfo instanceof Ci.nsIURI) {
places.push({ uri: aPlaceInfo });
}
else if (Array.isArray(aPlaceInfo)) {
places = places.concat(aPlaceInfo);
} else {
places.push(aPlaceInfo)
}
// Create mozIVisitInfo for each entry.
let now = Date.now();
for (let place of places) {
if (!place.title) {
place.title = "test visit for " + place.uri.spec;
}
place.visits = [{
transitionType: place.transition === undefined ? TRANSITION_LINK
: place.transition,
visitDate: place.visitDate || (now++) * 1000,
referrerURI: place.referrer
}];
}
aWindow.PlacesUtils.asyncHistory.updatePlaces(
places,
{
handleError: function AAV_handleError() {
throw ("Unexpected error in adding visit.");
},
handleResult: function () {},
handleCompletion: function UP_handleCompletion() {
if (aCallback)
aCallback();
}
}
);
}
/**
* Checks that the favicon for the given page matches the provided data.
*
* @param aPageURI
* nsIURI object for the page to check.
* @param aExpectedMimeType
* Expected MIME type of the icon, for example "image/png".
* @param aExpectedData
* Expected icon data, expressed as an array of byte values.
* @param aCallback
* This function is called after the check finished.
*/
function checkFaviconDataForPage(aPageURI, aExpectedMimeType, aExpectedData,
aWindow, aCallback) {
aWindow.PlacesUtils.favicons.getFaviconDataForPage(aPageURI,
function (aURI, aDataLen, aData, aMimeType) {
is(aExpectedMimeType, aMimeType, "Check expected MimeType");
is(aExpectedData.length, aData.length,
"Check favicon data for the given page matches the provided data");
checkGuidForURI(aPageURI);
aCallback();
});
}
/**
* Tests that a guid was set in moz_places for a given uri.
*
* @param aURI
* The uri to check.
* @param [optional] aGUID
* The expected guid in the database.
*/
function checkGuidForURI(aURI, aGUID) {
let guid = doGetGuidForURI(aURI);
if (aGUID) {
doCheckValidPlacesGuid(aGUID);
is(guid, aGUID, "Check equal guid for URIs");
}
}
/**
* Retrieves the guid for a given uri.
*
* @param aURI
* The uri to check.
* @return the associated the guid.
*/
function doGetGuidForURI(aURI) {
let stmt = DBConn().createStatement(
`SELECT guid
FROM moz_places
WHERE url_hash = hash(:url) AND url = :url`
);
stmt.params.url = aURI.spec;
ok(stmt.executeStep(), "Check get guid for uri from moz_places");
let guid = stmt.row.guid;
stmt.finalize();
doCheckValidPlacesGuid(guid);
return guid;
}
/**
* Tests if a given guid is valid for use in Places or not.
*
* @param aGuid
* The guid to test.
*/
function doCheckValidPlacesGuid(aGuid) {
ok(/^[a-zA-Z0-9\-_]{12}$/.test(aGuid), "Check guid for valid places");
}
/**
* Gets the database connection. If the Places connection is invalid it will
* try to create a new connection.
*
* @param [optional] aForceNewConnection
* Forces creation of a new connection to the database. When a
* connection is asyncClosed it cannot anymore schedule async statements,
* though connectionReady will keep returning true (Bug 726990).
*
* @return The database connection or null if unable to get one.
*/
function DBConn(aForceNewConnection) {
let gDBConn;
if (!aForceNewConnection) {
let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.DBConnection;
if (db.connectionReady)
return db;
}
// If the Places database connection has been closed, create a new connection.
if (!gDBConn || aForceNewConnection) {
let file = Services.dirsvc.get('ProfD', Ci.nsIFile);
file.append("places.sqlite");
let dbConn = gDBConn = Services.storage.openDatabase(file);
// Be sure to cleanly close this connection.
Services.obs.addObserver(function DBCloseCallback(aSubject, aTopic, aData) {
Services.obs.removeObserver(DBCloseCallback, aTopic);
dbConn.asyncClose();
}, "profile-before-change", false);
}
return gDBConn.connectionReady ? gDBConn : null;
}
function whenNewWindowLoaded(aOptions, aCallback) {
BrowserTestUtils.waitForNewWindow().then(aCallback);
OpenBrowserWindow(aOptions);
}
/**
* Asynchronously check a url is visited.
*
* @param aURI The URI.
* @param aExpectedValue The expected value.
* @return {Promise}
* @resolves When the check has been added successfully.
* @rejects JavaScript exception.
*/
function promiseIsURIVisited(aURI, aExpectedValue) {
return new Promise(resolve => {
PlacesUtils.asyncHistory.isURIVisited(aURI, function(unused, aIsVisited) {
resolve(aIsVisited);
});
});
}
function waitForCondition(condition, nextTest, errorMsg) {
let tries = 0;
let interval = setInterval(function() {
if (tries >= 30) {
ok(false, errorMsg);
moveOn();
}
let conditionPassed;
try {
conditionPassed = condition();
} catch (e) {
ok(false, e + "\n" + e.stack);
conditionPassed = false;
}
if (conditionPassed) {
moveOn();
}
tries++;
}, 200);
function moveOn() {
clearInterval(interval);
nextTest();
}
}

View file

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test post pages are not added to history</title>
</head>
<body>
<iframe name="post_iframe" id="post_iframe"></iframe>
<form method="post" action="http://example.com/tests/toolkit/components/places/tests/browser/history_post.sjs" target="post_iframe">
<input type="submit" id="submit"/>
</form>
</body>
</html>

View file

@ -0,0 +1,6 @@
function handleRequest(request, response)
{
response.setStatusLine("1.0", 200, "OK");
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
response.write("Ciao");
}

View file

@ -0,0 +1 @@
<!DOCTYPE html><html><body><p>Ciao!</p></body></html>

View file

@ -0,0 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
function handleRequest(request, response)
{
let page = "<!DOCTYPE html><html><body><p>Redirecting...</p></body></html>";
response.setStatusLine(request.httpVersion, "301", "Moved Permanently");
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Content-Length", page.length + "", false);
response.setHeader("Location", "redirect-target.html", false);
response.write(page);
}

View file

@ -0,0 +1,9 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
function handleRequest(request, response) {
response.setStatusLine("1.1", 301, "Found");
response.setHeader("Location", "final.html", false);
}

View file

@ -0,0 +1,9 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
function handleRequest(request, response) {
response.setStatusLine("1.1", 302, "Found");
response.setHeader("Location", "redirect_once.sjs", false);
}

View file

@ -0,0 +1,12 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
</head>
<body>
title1.html
</body>
</html>

View file

@ -0,0 +1,14 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Some title</title>
</head>
<body>
title2.html
</body>
</html>

View file

@ -0,0 +1,8 @@
"use strict";
module.exports = {
"extends": [
"../../../../../testing/mochitest/chrome.eslintrc.js",
"../../../../../testing/mochitest/mochitest.eslintrc.js"
]
};

Some files were not shown because too many files have changed in this diff Show more