Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
<!DOCTYPE HTML>
<html>
<head>
<title>WebExtension test</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="text/javascript">
"use strict";
const { ExtensionStorageIDB } = SpecialPowers.ChromeUtils.importESModule(
"resource://gre/modules/ExtensionStorageIDB.sys.mjs"
);
const storageTestHelpers = {
storageLocal: {
async writeData() {
await browser.storage.local.set({hello: "world"});
browser.test.sendMessage("finished");
},
async readData() {
const matchBrowserStorage = await browser.storage.local.get("hello").then(result => {
return (Object.keys(result).length == 1 && result.hello == "world");
});
browser.test.sendMessage("results", {matchBrowserStorage});
},
assertResults({results, keepOnUninstall}) {
if (keepOnUninstall) {
is(results.matchBrowserStorage, true, "browser.storage.local data is still present");
} else {
is(results.matchBrowserStorage, false, "browser.storage.local data was cleared");
}
},
},
storageSync: {
async writeData() {
await browser.storage.sync.set({hello: "world"});
browser.test.sendMessage("finished");
},
async readData() {
const matchBrowserStorage = await browser.storage.sync.get("hello").then(result => {
return (Object.keys(result).length == 1 && result.hello == "world");
});
browser.test.sendMessage("results", {matchBrowserStorage});
},
assertResults({results, keepOnUninstall}) {
if (keepOnUninstall) {
is(results.matchBrowserStorage, true, "browser.storage.sync data is still present");
} else {
is(results.matchBrowserStorage, false, "browser.storage.sync data was cleared");
}
},
},
webAPIs: {
async readData() {
let matchLocalStorage = (localStorage.getItem("hello") == "world");
let idbPromise = new Promise((resolve, reject) => {
let req = indexedDB.open("test");
req.onerror = e => {
reject(new Error(`indexedDB open failed with ${e.errorCode}`));
};
req.onupgradeneeded = () => {
// no database, data is not present
resolve(false);
};
req.onsuccess = e => {
let db = e.target.result;
let transaction = db.transaction("store", "readwrite");
let addreq = transaction.objectStore("store").get("hello");
addreq.onerror = addreqError => {
reject(new Error(`read from indexedDB failed with ${addreqError.errorCode}`));
};
addreq.onsuccess = () => {
let match = (addreq.result.value == "world");
resolve(match);
};
};
});
await idbPromise.then(matchIDB => {
let result = {matchLocalStorage, matchIDB};
browser.test.sendMessage("results", result);
});
},
async writeData() {
localStorage.setItem("hello", "world");
let idbPromise = new Promise((resolve, reject) => {
let req = indexedDB.open("test");
req.onerror = e => {
reject(new Error(`indexedDB open failed with ${e.errorCode}`));
};
req.onupgradeneeded = e => {
let db = e.target.result;
db.createObjectStore("store", {keyPath: "name"});
};
req.onsuccess = e => {
let db = e.target.result;
let transaction = db.transaction("store", "readwrite");
let addreq = transaction.objectStore("store")
.add({name: "hello", value: "world"});
addreq.onerror = addreqError => {
reject(new Error(`add to indexedDB failed with ${addreqError.errorCode}`));
};
addreq.onsuccess = () => {
resolve();
};
};
});
await idbPromise.then(() => {
browser.test.sendMessage("finished");
});
},
assertResults({results, keepOnUninstall}) {
if (keepOnUninstall) {
is(results.matchLocalStorage, true, "localStorage data is still present");
is(results.matchIDB, true, "indexedDB data is still present");
} else {
is(results.matchLocalStorage, false, "localStorage data was cleared");
is(results.matchIDB, false, "indexedDB data was cleared");
}
},
},
};
async function test_uninstall({extensionId, writeData, readData, assertResults}) {
// Set the pref to prevent cleaning up storage on uninstall in a separate prefEnv
// so we can pop it below, leaving flags set in the previous prefEnvs unmodified.
await SpecialPowers.pushPrefEnv({
set: [["extensions.webextensions.keepStorageOnUninstall", true]],
});
let extension = ExtensionTestUtils.loadExtension({
background: writeData,
manifest: {
browser_specific_settings: {gecko: {id: extensionId}},
permissions: ["storage"],
},
useAddonManager: "temporary",
});
await extension.startup();
await extension.awaitMessage("finished");
await extension.unload();
// Check that we can still see data we wrote to storage but clear the
// "leave storage" flag so our storaged gets cleared on the next uninstall.
// This effectively tests the keepUuidOnUninstall logic, which ensures
// that when we read storage again and check that it is cleared, that
// it is actually a meaningful test!
await SpecialPowers.popPrefEnv();
extension = ExtensionTestUtils.loadExtension({
background: readData,
manifest: {
browser_specific_settings: {gecko: {id: extensionId}},
permissions: ["storage"],
},
useAddonManager: "temporary",
});
await extension.startup();
let results = await extension.awaitMessage("results");
assertResults({results, keepOnUninstall: true});
await extension.unload();
// Read again. This time, our data should be gone.
extension = ExtensionTestUtils.loadExtension({
background: readData,
manifest: {
browser_specific_settings: {gecko: {id: extensionId}},
permissions: ["storage"],
},
useAddonManager: "temporary",
});
await extension.startup();
results = await extension.awaitMessage("results");
assertResults({results, keepOnUninstall: false});
await extension.unload();
}
add_task(async function test_setup_keep_uuid_on_uninstall() {
// Use a test-only pref to leave the addonid->uuid mapping around after
// uninstall so that we can re-attach to the same storage (this prefEnv
// is kept for this entire file and cleared automatically once all the
// tests in this file have been executed).
await SpecialPowers.pushPrefEnv({
set: [["extensions.webextensions.keepUuidOnUninstall", true]],
});
});
// Test extension indexedDB and localStorage storages get cleaned up when the
// extension is uninstalled.
add_task(async function test_uninstall_with_webapi_storages() {
await test_uninstall({
extensionId: "storage.cleanup-WebAPIStorages@tests.mozilla.org",
...(storageTestHelpers.webAPIs),
});
});
// Test browser.storage.local with JSONFile backend gets cleaned up when the
// extension is uninstalled.
add_task(async function test_uninistall_with_storage_local_file_backend() {
await SpecialPowers.pushPrefEnv({
set: [[ExtensionStorageIDB.BACKEND_ENABLED_PREF, false]],
});
await test_uninstall({
extensionId: "storage.cleanup-JSONFileBackend@tests.mozilla.org",
...(storageTestHelpers.storageLocal),
});
await SpecialPowers.popPrefEnv();
});
// Repeat the cleanup test when the storage.local IndexedDB backend is enabled.
add_task(async function test_uninistall_with_storage_local_idb_backend() {
await SpecialPowers.pushPrefEnv({
set: [[ExtensionStorageIDB.BACKEND_ENABLED_PREF, true]],
});
await test_uninstall({
extensionId: "storage.cleanup-IDBBackend@tests.mozilla.org",
...(storageTestHelpers.storageLocal),
});
await SpecialPowers.popPrefEnv();
});
// Legacy storage.sync backend is still being used on GeckoView builds.
const storageSyncOldKintoBackend = SpecialPowers.Services.prefs.getBoolPref(
"webextensions.storage.sync.kinto",
false
);
// Verify browser.storage.sync rust backend is also cleared on uninstall.
async function test_uninistall_with_storage_sync() {
await test_uninstall({
extensionId: "storage.cleanup-sync@tests.mozilla.org",
...(storageTestHelpers.storageSync),
});
}
// NOTE: ideally we would be using a skip_if option on the add_task call,
// but we don't support that in the add_task defined in mochitest-plain.
if (!storageSyncOldKintoBackend) {
add_task(test_uninistall_with_storage_sync);
}
</script>
</body>
</html>