Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: privateBrowsing
- Manifest: dom/localstorage/test/unit/xpcshell.toml
/**
* Any copyright is dedicated to the Public Domain.
*/
// A negative LocalStorage usage (the running counter in `database.usage`,
// mirrored into the `usage` file) can be persisted. When the
// origin is then initialized from disk (e.g. when CheckIfUsageIsConsistent
// fails, or after a build id change), usage must be recomputed from the rows
// and both persisted copies repaired, instead of the negative value being
add_task(async function testSteps() {
const key = "key1";
const value = repeatChar(1000, ".");
const expectedUsage = key.length + value.length;
const principal = getDefaultPrincipal();
const usageFile = getRelativeFile(
"storage/default/http+++example.com/ls/usage"
);
const usageJournalFile = getRelativeFile(
"storage/default/http+++example.com/ls/usage-journal"
);
const databaseFile = getRelativeFile(
"storage/default/http+++example.com/ls/data.sqlite"
);
info("Setting prefs");
// Make origin initialization go through QuotaClient::InitOrigin (which reads
// the usage file) instead of trusting the storage.sqlite origin row written
// at the previous clean shutdown.
// Alternatively, we could modify the database to mark the origin as dirty, or
// corrupt the usage value to an underflow value to force skipping the cache,
// but changing the pref is easier, and already done in other tests, so it's
// probably fine.
Services.prefs.setBoolPref("dom.quotaManager.loadQuotaFromCache", false);
info("Stage 1 - Storing an item and flushing it to disk");
let request = clear();
await requestFinished(request);
let storage = getLocalStorage(principal);
storage.setItem(key, value);
request = resetClient(principal);
await requestFinished(request);
request = reset();
await requestFinished(request);
ok(usageFile.exists(), "Usage file exists");
ok(!usageJournalFile.exists(), "No usage journal");
is(
await readUsageFromUsageFile(usageFile),
expectedUsage,
"Usage file holds the correct usage"
);
info("Stage 2 - Replacing the usage file content with a negative counter");
// Cookie 0x420a420a followed by int64 -6, as UpdateUsageFile would write it.
await IOUtils.write(
usageFile.path,
new Uint8Array([
0x42, 0x0a, 0x42, 0x0a, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa,
])
);
info("Stage 3 - Initializing the origin from disk");
request = initStorage();
await requestFinished(request);
request = initTemporaryStorage();
await requestFinished(request);
request = initTemporaryOrigin(persistenceFor(principal), principal, false);
await requestFinished(request);
request = getCachedOriginUsage(principal);
await requestFinished(request);
is(request.result, expectedUsage, "In-memory usage recomputed from rows");
request = getOriginUsage(principal);
await requestFinished(request);
is(
request.result.usage,
expectedUsage,
"Reported usage recomputed from rows"
);
is(
await readUsageFromUsageFile(usageFile),
expectedUsage,
"Usage file repaired"
);
info("Stage 4 - Checking the running counter in the database");
request = reset();
await requestFinished(request);
const { Sqlite } = ChromeUtils.importESModule(
"resource://gre/modules/Sqlite.sys.mjs"
);
const connection = await Sqlite.openConnection({
path: databaseFile.path,
readOnly: true,
});
try {
const rows = await connection.execute("SELECT usage FROM database;");
is(rows.length, 1, "One database row");
is(
rows[0].getResultByName("usage"),
expectedUsage,
"database.usage repaired"
);
} finally {
await connection.close();
}
info("Stage 5 - Removing the item");
storage = getLocalStorage(principal);
storage.removeItem(key);
request = resetClient(principal);
await requestFinished(request);
request = getOriginUsage(principal);
await requestFinished(request);
is(request.result.usage, 0, "Usage is zero after removing the item");
});