Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Errors
- This test gets skipped with pattern: os == 'android'
- This test failed 4 times in the preceding 30 days. quicksearch this test
- Manifest: browser/components/backup/tests/xpcshell/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
const { setTimeout } = ChromeUtils.importESModule(
"resource://gre/modules/Timer.sys.mjs"
);
const { NetUtil } = ChromeUtils.importESModule(
"resource://gre/modules/NetUtil.sys.mjs"
);
const { PlacesTestUtils } = ChromeUtils.importESModule(
);
const { PlacesUtils } = ChromeUtils.importESModule(
"resource://gre/modules/PlacesUtils.sys.mjs"
);
const { TestUtils } = ChromeUtils.importESModule(
);
const { DownloadHistory } = ChromeUtils.importESModule(
"resource://gre/modules/DownloadHistory.sys.mjs"
);
const { AddonTestUtils } = ChromeUtils.importESModule(
);
const { ExtensionTestUtils } = ChromeUtils.importESModule(
);
const { formAutofillStorage } = ChromeUtils.importESModule(
"resource://autofill/FormAutofillStorage.sys.mjs"
);
const { Sanitizer } = ChromeUtils.importESModule(
"resource:///modules/Sanitizer.sys.mjs"
);
const { NewTabUtils } = ChromeUtils.importESModule(
"resource://gre/modules/NewTabUtils.sys.mjs"
);
const { CookieXPCShellUtils } = ChromeUtils.importESModule(
);
ExtensionTestUtils.init(this);
AddonTestUtils.init(this);
AddonTestUtils.createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1");
const nsLoginInfo = new Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo,
"init"
);
/**
* This suite of tests ensures that the current backup will be deleted, and
* a new one generated when certain user actions occur.
*/
/**
* Adds a history visit to the Places database.
*
* @param {string} uriString
* A string representation of the URI to add a visit for.
* @param {number} [timestamp=Date.now()]
* The timestamp for the visit to be used. By default, this is the current
* date and time.
* @returns {Promise<undefined>}
*/
async function addTestHistory(uriString, timestamp = Date.now()) {
let uri = NetUtil.newURI(uriString);
await PlacesTestUtils.addVisits({
uri,
transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
visitDate: timestamp * 1000,
});
}
let gCookieCounter = 0;
const COOKIE_HOST = "example.com";
const COOKIE_PATH = "/";
const COOKIE_ORIGIN_ATTRIBUTES = Object.freeze({});
/**
* Adds a new non-session cookie to the cookie database with the host set
* as COOKIE_HOST, the path as COOKIE_PATH, with the origin attributes of
* COOKIE_ORIGIN_ATTRIBUTES and a generated name and value.
*
* @param {boolean} isSessionCookie
* True if the cookie should be a session cookie.
* @returns {string}
* The name of the cookie that was generated.
*/
function addTestCookie(isSessionCookie) {
gCookieCounter++;
let name = `Cookie name: ${gCookieCounter}`;
Services.cookies.add(
COOKIE_HOST,
COOKIE_PATH,
name,
`Cookie value: ${gCookieCounter}`,
false,
false,
isSessionCookie,
Date.now() / 1000 + 1,
COOKIE_ORIGIN_ATTRIBUTES,
Ci.nsICookie.SAMESITE_NONE,
Ci.nsICookie.SCHEME_HTTP
);
return name;
}
/**
* A helper function that sets up a BackupService to be instrumented to detect
* a backup regeneration, and then runs an async taskFn to ensure that the
* regeneration occurs.
*
* @param {Function} taskFn
* The async function to run after the BackupService has been set up. It is
* not passed any arguments.
* @param {string} msg
* The message to write in the assertion when the regeneration occurs.
*/
async function expectRegeneration(taskFn, msg) {
let sandbox = sinon.createSandbox();
// Override the default REGENERATION_DEBOUNCE_RATE_MS to 0 so that we don't
// have to wait long for the debouncer to fire. We need to do this before
// we construct the BackupService that we'll use for the test.
sandbox.stub(BackupService, "REGENERATION_DEBOUNCE_RATE_MS").get(() => 0);
let bs = new BackupService();
// Now we set up some stubs on the BackupService to detect calls to
// deleteLastBackup and createbackupOnIdleDispatch, which are both called
// on regeneration.
let deleteDeferred = Promise.withResolvers();
sandbox.stub(bs, "deleteLastBackup").callsFake(() => {
Assert.ok(true, "Saw deleteLastBackup call");
deleteDeferred.resolve();
return Promise.resolve();
});
let createBackupDeferred = Promise.withResolvers();
sandbox.stub(bs, "createBackupOnIdleDispatch").callsFake(() => {
Assert.ok(true, "Saw createBackupOnIdleDispatch call");
createBackupDeferred.resolve();
return Promise.resolve();
});
// Creating a new backup will only occur if scheduled backups are enabled,
// so let's set the pref...
Services.prefs.setBoolPref("browser.backup.scheduled.enabled", true);
// But also stub out `onIdle` so that we don't get any interference during
// our test by the idle service.
sandbox.stub(bs, "onIdle").returns();
bs.initBackupScheduler();
await taskFn();
let regenerationPromises = [
deleteDeferred.promise,
createBackupDeferred.promise,
];
// We'll wait for 1 second before considering the regeneration a bust.
let timeoutPromise = new Promise((resolve, reject) =>
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(() => {
reject();
}, 1000)
);
try {
await Promise.race([Promise.all(regenerationPromises), timeoutPromise]);
Assert.ok(true, msg);
} catch (e) {
Assert.ok(false, "Timed out waiting for regeneration.");
}
bs.uninitBackupScheduler();
sandbox.restore();
}
/**
* A helper function that sets up a BackupService to be instrumented to detect
* a backup regeneration, and then runs an async taskFn to ensure that the
* regeneration DOES NOT occur.
*
* @param {Function} taskFn
* The async function to run after the BackupService has been set up. It is
* not passed any arguments.
* @param {string} msg
* The message to write in the assertion when the regeneration does not occur
* within the timeout.
*/
async function expectNoRegeneration(taskFn, msg) {
let sandbox = sinon.createSandbox();
// Override the default REGENERATION_DEBOUNCE_RATE_MS to 0 so that we don't
// have to wait long for the debouncer to fire. We need to do this before
// we construct the BackupService that we'll use for the test.
sandbox.stub(BackupService, "REGENERATION_DEBOUNCE_RATE_MS").get(() => 0);
let bs = new BackupService();
// Now we set up some stubs on the BackupService to detect calls to
// deleteLastBackup and createbackupOnIdleDispatch, which are both called
// on regeneration. We share the same Promise here because in either of these
// being called, this test is a failure.
let regenerationPromise = Promise.withResolvers();
sandbox.stub(bs, "deleteLastBackup").callsFake(() => {
Assert.ok(false, "Unexpectedly saw deleteLastBackup call");
regenerationPromise.reject();
return Promise.resolve();
});
sandbox.stub(bs, "createBackupOnIdleDispatch").callsFake(() => {
Assert.ok(false, "Unexpectedly saw createBackupOnIdleDispatch call");
regenerationPromise.reject();
return Promise.resolve();
});
// Creating a new backup will only occur if scheduled backups are enabled,
// so let's set the pref...
Services.prefs.setBoolPref("browser.backup.scheduled.enabled", true);
// But also stub out `onIdle` so that we don't get any interference during
// our test by the idle service.
sandbox.stub(bs, "onIdle").returns();
bs.initBackupScheduler();
await taskFn();
// We'll wait for 1 second, and if we don't see the regeneration attempt,
// we'll assume it's not coming.
let timeoutPromise = new Promise(resolve =>
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(() => {
Assert.ok(true, "Saw no regeneration within 1 second.");
resolve();
}, 1000)
);
try {
await Promise.race([regenerationPromise.promise, timeoutPromise]);
Assert.ok(true, msg);
} catch (e) {
Assert.ok(false, "Saw an unexpected regeneration.");
}
bs.uninitBackupScheduler();
sandbox.restore();
}
add_setup(() => {
CookieXPCShellUtils.createServer({ hosts: ["example.com"] });
Services.prefs.setBoolPref("dom.security.https_first", false);
// Allow all cookies.
Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
Services.prefs.setBoolPref(
"network.cookieJarSettings.unblocked_for_testing",
true
);
});
/**
* Tests that backup regeneration occurs on the page-removed PlacesObserver
* event that indicates manual user deletion of a page from history.
*/
add_task(async function test_page_removed_reason_deleted() {
await addTestHistory(PAGE_URI);
await expectRegeneration(async () => {
await PlacesUtils.history.remove(PAGE_URI);
}, "Saw regeneration on page-removed via user deletion.");
});
/**
* Tests that backup regeneration does not occur on the page-removed
* PlacesObserver event that indicates automatic deletion of a page from
* history.
*/
add_task(async function test_page_removed_reason_expired() {
await addTestHistory(
PAGE_URI,
0 /* Timestamp at 0 is wayyyyy in the past, in the 1960's - it's the UNIX epoch start date */
);
await expectNoRegeneration(async () => {
// This is how the Places tests force expiration, so we'll do it the same
// way.
let promise = TestUtils.topicObserved(
PlacesUtils.TOPIC_EXPIRATION_FINISHED
);
let expire = Cc["@mozilla.org/places/expiration;1"].getService(
Ci.nsIObserver
);
expire.observe(null, "places-debug-start-expiration", -1);
await promise;
}, "Saw no regeneration on page-removed via expiration.");
});
/**
* Tests that backup regeneration occurs on the history-cleared PlacesObserver
* event that indicates clearing of all user history.
*/
add_task(async function test_history_cleared() {
await addTestHistory(PAGE_URI);
await expectRegeneration(async () => {
await PlacesUtils.history.clear();
}, "Saw regeneration on history-cleared.");
});
/**
* Tests that backup regeneration occurs when removing a download from history.
*/
add_task(async function test_download_removed() {
const FAKE_FILE_PATH = PathUtils.join(PathUtils.tempDir, "somefile.zip");
let download = {
},
target: { path: FAKE_FILE_PATH },
};
await DownloadHistory.addDownloadToHistory(download);
await expectRegeneration(async () => {
await PlacesUtils.history.remove(download.source.url);
}, "Saw regeneration on download removal.");
});
/**
* Tests that backup regeneration occurs when clearing all downloads.
*/
add_task(async function test_all_downloads_removed() {
const FAKE_FILE_PATH = PathUtils.join(PathUtils.tempDir, "somefile.zip");
let download1 = {
},
target: { path: FAKE_FILE_PATH },
};
let download2 = {
},
target: { path: FAKE_FILE_PATH },
};
await DownloadHistory.addDownloadToHistory(download1);
await DownloadHistory.addDownloadToHistory(download2);
await expectRegeneration(async () => {
await PlacesUtils.history.removeVisitsByFilter({
transition: PlacesUtils.history.TRANSITIONS.DOWNLOAD,
});
}, "Saw regeneration on all downloads removed.");
});
/**
* Tests that backup regeneration occurs when removing a password.
*/
add_task(async function test_password_removed() {
let login = new nsLoginInfo(
null,
"notifyu1",
"notifyp1",
"user",
"pass"
);
await Services.logins.addLoginAsync(login);
await expectRegeneration(async () => {
Services.logins.removeLogin(login);
}, "Saw regeneration on password removed.");
});
/**
* Tests that backup regeneration occurs when all passwords are removed.
*/
add_task(async function test_all_passwords_removed() {
let login1 = new nsLoginInfo(
null,
"notifyu1",
"notifyp1",
"user",
"pass"
);
let login2 = new nsLoginInfo(
null,
"",
"notifyp1",
"",
"pass"
);
await Services.logins.addLoginAsync(login1);
await Services.logins.addLoginAsync(login2);
await expectRegeneration(async () => {
Services.logins.removeAllLogins();
}, "Saw regeneration on all passwords removed.");
});
/**
* Tests that backup regeneration occurs when removing a bookmark.
*/
add_task(async function test_bookmark_removed() {
let bookmark = await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "data:text/plain,Content",
title: "Regeneration Test Bookmark",
});
await expectRegeneration(async () => {
await PlacesUtils.bookmarks.remove(bookmark);
}, "Saw regeneration on bookmark removed.");
});
/**
* Tests that backup regeneration occurs when all bookmarks are removed.
*/
add_task(async function test_all_bookmarks_removed() {
await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "data:text/plain,Content",
title: "Regeneration Test Bookmark 1",
});
await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "data:text/plain,Content2",
title: "Regeneration Test Bookmark 2",
});
await expectRegeneration(async () => {
await PlacesUtils.bookmarks.eraseEverything();
}, "Saw regeneration on all bookmarks removed.");
});
/**
* Tests that backup regeneration occurs when an addon is uninstalled.
*/
add_task(async function test_addon_uninstalled() {
await AddonTestUtils.promiseStartupManager();
let testExtension = ExtensionTestUtils.loadExtension({
manifest: {
name: "Some test extension",
browser_specific_settings: {
gecko: { id: "test-backup-regeneration@ext-0" },
},
},
useAddonManager: "temporary",
});
await testExtension.startup();
await expectRegeneration(async () => {
await testExtension.unload();
}, "Saw regeneration on test extension uninstall.");
});
/**
* Tests that backup regeneration occurs when removing a payment method.
*/
add_task(async function test_payment_method_removed() {
await formAutofillStorage.initialize();
let guid = await formAutofillStorage.creditCards.add({
"cc-name": "Foxy the Firefox",
"cc-number": "5555555555554444",
"cc-exp-month": 5,
"cc-exp-year": 2099,
});
await expectRegeneration(async () => {
await formAutofillStorage.creditCards.remove(guid);
}, "Saw regeneration on payment method removal.");
});
/**
* Tests that backup regeneration occurs when removing an address.
*/
add_task(async function test_address_removed() {
await formAutofillStorage.initialize();
let guid = await formAutofillStorage.addresses.add({
"given-name": "John",
"additional-name": "R.",
"family-name": "Smith",
organization: "World Wide Web Consortium",
"street-address": "32 Vassar Street\\\nMIT Room 32-G524",
"address-level2": "Cambridge",
"address-level1": "MA",
"postal-code": "02139",
country: "US",
tel: "+15195555555",
email: "user@example.com",
});
await expectRegeneration(async () => {
await formAutofillStorage.addresses.remove(guid);
}, "Saw regeneration on address removal.");
});
/**
* Tests that backup regeneration occurs after any kind of data sanitization.
*/
add_task(async function test_sanitization() {
await expectRegeneration(async () => {
await Sanitizer.sanitize(["cookiesAndStorage"]);
}, "Saw regeneration on sanitization of cookies and storage.");
await expectRegeneration(async () => {
await Sanitizer.sanitize(["siteSettings"]);
}, "Saw regeneration on sanitization of site settings.");
});
/**
* Tests that backup regeneration occurs after a permission is removed.
*/
add_task(async function test_permission_removed() {
let principal =
Services.scriptSecurityManager.createContentPrincipalFromOrigin(
);
const PERMISSION_TYPE = "desktop-notification";
Services.perms.addFromPrincipal(
principal,
PERMISSION_TYPE,
Services.perms.ALLOW_ACTION
);
await expectRegeneration(async () => {
Services.perms.removeFromPrincipal(principal, PERMISSION_TYPE);
}, "Saw regeneration on permission removal.");
});
/**
* Tests that backup regeneration occurs when persistent and session cookies are
* removed.
*/
add_task(async function test_cookies_removed() {
for (let isSessionCookie of [false, true]) {
Services.cookies.removeAll();
// First, let's remove a single cookie by host, path, name and origin
// attrbutes.
let name = addTestCookie(isSessionCookie);
if (isSessionCookie) {
Assert.equal(
Services.cookies.sessionCookies.length,
1,
"Make sure we actually added a session cookie."
);
} else {
Assert.equal(
Services.cookies.sessionCookies.length,
0,
"Make sure we actually added a persistent cookie."
);
}
await expectRegeneration(async () => {
Services.cookies.remove(
COOKIE_HOST,
name,
COOKIE_PATH,
COOKIE_ORIGIN_ATTRIBUTES
);
}, "Saw regeneration on single cookie removal.");
// Now remove all cookies for a particular host.
addTestCookie(isSessionCookie);
addTestCookie(isSessionCookie);
await expectRegeneration(async () => {
Services.cookies.removeCookiesFromExactHost(COOKIE_HOST, "{}");
}, "Saw regeneration on cookie removal by host.");
// Now remove all cookies.
const COOKIES_TO_ADD = 10;
for (let i = 0; i < COOKIES_TO_ADD; ++i) {
addTestCookie(isSessionCookie);
}
await expectRegeneration(async () => {
Services.cookies.removeAll();
}, "Saw regeneration on all cookie removal.");
}
});
/**
* Tests that backup regeneration does not occur if a cookie is removed due
* to expiry.
*/
add_task(async function test_cookies_not_removed_expiry() {
await expectNoRegeneration(async () => {
const COOKIE = "test=test";
await CookieXPCShellUtils.setCookieToDocument(
COOKIE
);
// Now expire that cookie by using the same value, but setting the expires
// directive to sometime wayyyyy in the past.
const EXPIRE = `${COOKIE}; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/`;
await CookieXPCShellUtils.setCookieToDocument(
EXPIRE
);
}, "Saw no regeneration on cookie expiry.");
});
/**
* Tests that backup regeneration occurs when newtab links are blocked.
*/
add_task(async function test_newtab_link_blocked() {
NewTabUtils.init();
await expectRegeneration(async () => {
}, "Saw regeneration on the blocking of a newtab link");
});