Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/**
* Any copyright is dedicated to the Public Domain.
*/
/**
* Recorder script for the schema 3→4 quota manager migration test.
*
* Run this against a Firefox built from main (kCacheVersion = 3):
* ./mach xpcshell-test \
* dom/quota/test/xpcshell/upgrades/make_upgradeStorageFrom3to4.js \
* --interactive
*
* The script creates two origins populated with IDB, localStorage, Cache API,
* and OPFS data, persists one of them, resets the quota manager to flush all
* state to disk, and then packages everything into a zip archive written to
* the source tree alongside this file.
*/
const { IndexedDBUtils } = ChromeUtils.importESModule(
);
const { LocalStorageUtils } = ChromeUtils.importESModule(
);
const { PrincipalUtils } = ChromeUtils.importESModule(
);
const { QuotaUtils } = ChromeUtils.importESModule(
);
function enumerateTree(entryList, dir, dirPath) {
entryList.push({ path: dirPath, file: dir });
const entries = dir.directoryEntries;
let nextFile;
while ((nextFile = entries.nextFile)) {
const childPath = `${dirPath}/${nextFile.leafName}`;
if (nextFile.isDirectory()) {
enumerateTree(entryList, nextFile, childPath);
} else {
entryList.push({ path: childPath, file: nextFile });
}
}
}
// nsIZipWriter uses PRTime (microseconds). 1980-01-01 is the minimum ZIP date.
const kMinZipPRTime = 315532800 * 1000 * 1000;
function zipProfile(zipFile, profileDir) {
const zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance(
Ci.nsIZipWriter
);
zipWriter.open(zipFile, 0x04 | 0x08 | 0x20);
const entryList = [];
const storageDir = profileDir.clone();
storageDir.append("storage");
const defaultDir = storageDir.clone();
defaultDir.append("default");
entryList.push({ path: "storage", file: storageDir });
entryList.push({ path: "storage/default", file: defaultDir });
for (const origin of ["https+++www.example.org", "https+++www.example.com"]) {
const originDir = defaultDir.clone();
originDir.append(origin);
if (originDir.exists()) {
enumerateTree(entryList, originDir, `storage/default/${origin}`);
}
}
const permanentDir = storageDir.clone();
permanentDir.append("permanent");
if (permanentDir.exists()) {
entryList.push({ path: "storage/permanent", file: permanentDir });
const chromeDir = permanentDir.clone();
chromeDir.append("chrome");
if (chromeDir.exists()) {
enumerateTree(entryList, chromeDir, "storage/permanent/chrome");
}
}
const storageSqliteFile = profileDir.clone();
storageSqliteFile.append("storage.sqlite");
if (storageSqliteFile.exists()) {
entryList.push({ path: "storage.sqlite", file: storageSqliteFile });
}
for (const { path, file } of entryList) {
if (path.endsWith("-wal") || path.endsWith("-shm")) {
continue;
}
const timestamp = Math.max(file.lastModifiedTime * 1000, kMinZipPRTime);
if (file.isDirectory()) {
zipWriter.addEntryDirectory(path, timestamp, false);
} else {
const istream = Cc[
"@mozilla.org/network/file-input-stream;1"
].createInstance(Ci.nsIFileInputStream);
istream.init(file, -1, -1, 0);
zipWriter.addEntryStream(
path,
timestamp,
Ci.nsIZipWriter.COMPRESSION_DEFAULT,
istream,
false
);
istream.close();
}
}
zipWriter.close();
}
async function sandboxOp(principal, globalProperties, options, scriptSource) {
const sandbox = new Cu.Sandbox(principal, {
wantGlobalProperties: globalProperties,
...options,
});
const promise = new Promise(function (resolve, reject) {
sandbox.resolve = resolve;
sandbox.reject = reject;
});
Cu.evalInSandbox(
`(async function() { ${scriptSource} })().then(resolve, reject);`,
sandbox
);
return promise;
}
async function populateOrigin(principal, originLabel) {
// IndexedDB: create object store "store1" with one record keyed "seed"
{
const request = indexedDB.openForPrincipal(principal, "db1", 1);
await openDBRequestUpgradeNeeded(request);
const db = request.result;
const store = db.createObjectStore("store1");
store.put(`${originLabel}-idb`, "seed");
await IndexedDBUtils.requestFinished(request);
db.close();
}
// The system principal has no URI so localStorage, Cache API, and OPFS are
// not meaningful for it; skip them and rely on IDB alone for that origin.
if (principal.isSystemPrincipal) {
return;
}
// localStorage: set key1
{
const storage = LocalStorageUtils.createStorage(principal);
storage.setItem("key1", `${originLabel}-ls`);
storage.close();
}
// Cache API: store a response for https://example/seed in cache "c1"
{
const seedValue = JSON.stringify(`${originLabel}-cache`);
await sandboxOp(
principal,
["caches", "fetch"],
{},
`
const cache = await caches.open("c1");
await cache.put(
new Request("https://example/seed"),
new Response(${seedValue})
);
`
);
}
// OPFS: write seed.txt
{
const seedValue = JSON.stringify(`${originLabel}-opfs`);
await sandboxOp(
principal,
["storage"],
{ forceSecureContext: true },
`
const root = await storage.getDirectory();
const fh = await root.getFileHandle("seed.txt", { create: true });
const w = await fh.createWritable();
await w.write(${seedValue});
await w.close();
`
);
}
}
async function makeProfile() {
info("Populating https://www.example.org (temporary)");
const orgPrincipal = PrincipalUtils.createPrincipal(
);
await populateOrigin(orgPrincipal, "example.org");
info("Populating https://www.example.com (to be persisted)");
const comPrincipal = PrincipalUtils.createPrincipal(
);
await populateOrigin(comPrincipal, "example.com");
info("Persisting https://www.example.com");
{
const request = Services.qms.persist(comPrincipal);
await QuotaUtils.requestFinished(request);
}
info("Populating chrome (system principal, lands in storage/permanent/)");
const chromePrincipal = Services.scriptSecurityManager.getSystemPrincipal();
await populateOrigin(chromePrincipal, "chrome");
info("Resetting quota manager to flush state to disk");
{
const request = Services.qms.reset();
await QuotaUtils.requestFinished(request);
}
const profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
const currentDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
const zipFile = currentDir.clone();
zipFile.append("upgradeStorageFrom3to4_profile.zip");
if (zipFile.exists()) {
zipFile.remove(false);
}
info("Writing zip archive");
zipProfile(zipFile, profileDir);
info(`ZIP written to: ${zipFile.path}`);
}
async function testSteps() {
add_task(
{
pref_set: [
["dom.storage.testing", true],
["dom.storage.client_validation", false],
],
},
makeProfile
);
}