745 lines
18 KiB
JavaScript
745 lines
18 KiB
JavaScript
/**
|
|
* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
|
*/
|
|
|
|
const NS_OK = Cr.NS_OK;
|
|
const NS_ERROR_FAILURE = Cr.NS_ERROR_FAILURE;
|
|
const NS_ERROR_UNEXPECTED = Cr.NS_ERROR_UNEXPECTED;
|
|
const NS_ERROR_FILE_NO_DEVICE_SPACE = Cr.NS_ERROR_FILE_NO_DEVICE_SPACE;
|
|
|
|
const loggingEnabled = false;
|
|
|
|
var testGenerator;
|
|
|
|
loadScript("dom/quota/test/common/xpcshell.js");
|
|
|
|
function log(msg) {
|
|
if (loggingEnabled) {
|
|
info(msg);
|
|
}
|
|
}
|
|
|
|
function is(a, b, msg) {
|
|
Assert.equal(a, b, msg);
|
|
}
|
|
|
|
function ok(cond, msg) {
|
|
Assert.ok(!!cond, msg);
|
|
}
|
|
|
|
function todo(cond, msg) {
|
|
todo_check_true(cond);
|
|
}
|
|
|
|
function run_test() {
|
|
runTest();
|
|
}
|
|
|
|
if (!this.runTest) {
|
|
this.runTest = function () {
|
|
do_get_profile();
|
|
|
|
enableStorageTesting();
|
|
enableTesting();
|
|
|
|
// In order to support converting tests to using async functions from using
|
|
// generator functions, we detect async functions by checking the name of
|
|
// function's constructor.
|
|
Assert.ok(
|
|
typeof testSteps === "function",
|
|
"There should be a testSteps function"
|
|
);
|
|
if (testSteps.constructor.name === "AsyncFunction") {
|
|
// Do run our existing cleanup function that would normally be called by
|
|
// the generator's call to finishTest().
|
|
registerCleanupFunction(function () {
|
|
resetStorageTesting();
|
|
resetTesting();
|
|
});
|
|
|
|
add_task(testSteps);
|
|
|
|
// Since we defined run_test, we must invoke run_next_test() to start the
|
|
// async test.
|
|
run_next_test();
|
|
} else {
|
|
Assert.ok(
|
|
testSteps.constructor.name === "GeneratorFunction",
|
|
"Unsupported function type"
|
|
);
|
|
|
|
do_test_pending();
|
|
|
|
testGenerator = testSteps();
|
|
testGenerator.next();
|
|
}
|
|
};
|
|
}
|
|
|
|
function finishTest() {
|
|
resetStorageTesting();
|
|
resetTesting();
|
|
|
|
executeSoon(function () {
|
|
do_test_finished();
|
|
});
|
|
}
|
|
|
|
function grabArgAndContinueHandler(arg) {
|
|
testGenerator.next(arg);
|
|
}
|
|
|
|
function continueToNextStep() {
|
|
executeSoon(function () {
|
|
testGenerator.next();
|
|
});
|
|
}
|
|
|
|
function continueToNextStepSync() {
|
|
testGenerator.next();
|
|
}
|
|
|
|
function enableTesting() {
|
|
SpecialPowers.setBoolPref(
|
|
"dom.storage.enable_unsupported_legacy_implementation",
|
|
false
|
|
);
|
|
}
|
|
|
|
function resetTesting() {
|
|
SpecialPowers.clearUserPref(
|
|
"dom.storage.enable_unsupported_legacy_implementation"
|
|
);
|
|
}
|
|
|
|
function setGlobalLimit(globalLimit) {
|
|
SpecialPowers.setIntPref(
|
|
"dom.quotaManager.temporaryStorage.fixedLimit",
|
|
globalLimit
|
|
);
|
|
}
|
|
|
|
function resetGlobalLimit() {
|
|
SpecialPowers.clearUserPref("dom.quotaManager.temporaryStorage.fixedLimit");
|
|
}
|
|
|
|
function storageInitialized(callback) {
|
|
let request = SpecialPowers._getQuotaManager().storageInitialized();
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function persistentStorageInitialized(callback) {
|
|
let request = SpecialPowers._getQuotaManager().persistentStorageInitialized();
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function temporaryStorageInitialized(callback) {
|
|
let request = SpecialPowers._getQuotaManager().temporaryStorageInitialized();
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function persistentOriginInitialized(principal, callback) {
|
|
let request =
|
|
SpecialPowers._getQuotaManager().persistentOriginInitialized(principal);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function temporaryOriginInitialized(persistence, principal, callback) {
|
|
let request = SpecialPowers._getQuotaManager().temporaryOriginInitialized(
|
|
persistence,
|
|
principal
|
|
);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function init(callback) {
|
|
let request = SpecialPowers._getQuotaManager().init();
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function initializePersistentStorage(callback) {
|
|
let request = SpecialPowers._getQuotaManager().initializePersistentStorage();
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function initTemporaryStorage(callback) {
|
|
let request = SpecialPowers._getQuotaManager().initTemporaryStorage();
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function initPersistentOrigin(principal, callback) {
|
|
let request =
|
|
SpecialPowers._getQuotaManager().initializePersistentOrigin(principal);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function initTemporaryOrigin(
|
|
persistence,
|
|
principal,
|
|
createIfNonExistent = true,
|
|
callback
|
|
) {
|
|
let request = SpecialPowers._getQuotaManager().initializeTemporaryOrigin(
|
|
persistence,
|
|
principal,
|
|
createIfNonExistent
|
|
);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function initPersistentClient(principal, client, callback) {
|
|
let request = SpecialPowers._getQuotaManager().initializePersistentClient(
|
|
principal,
|
|
client
|
|
);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function initTemporaryClient(
|
|
persistence,
|
|
principal,
|
|
client,
|
|
createIfNonExistent = true,
|
|
callback
|
|
) {
|
|
let request = SpecialPowers._getQuotaManager().initializeTemporaryClient(
|
|
persistence,
|
|
principal,
|
|
client,
|
|
createIfNonExistent
|
|
);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function getFullOriginMetadata(persistence, principal, callback) {
|
|
const request = SpecialPowers._getQuotaManager().getFullOriginMetadata(
|
|
persistence,
|
|
principal
|
|
);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function clearClient(principal, client, persistence, callback) {
|
|
let request = SpecialPowers._getQuotaManager().clearStoragesForClient(
|
|
principal,
|
|
client,
|
|
persistence
|
|
);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function clearOrigin(principal, persistence, callback) {
|
|
let request = SpecialPowers._getQuotaManager().clearStoragesForPrincipal(
|
|
principal,
|
|
persistence
|
|
);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function clearOriginsByPrefix(principal, persistence, callback) {
|
|
let request = SpecialPowers._getQuotaManager().clearStoragesForOriginPrefix(
|
|
principal,
|
|
persistence
|
|
);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function clearPrivateBrowsing(callback) {
|
|
let request =
|
|
SpecialPowers._getQuotaManager().clearStoragesForPrivateBrowsing();
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function resetClient(principal, client) {
|
|
let request = Services.qms.resetStoragesForClient(
|
|
principal,
|
|
client,
|
|
"default"
|
|
);
|
|
|
|
return request;
|
|
}
|
|
|
|
function persist(principal, callback) {
|
|
let request = SpecialPowers._getQuotaManager().persist(principal);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function persisted(principal, callback) {
|
|
let request = SpecialPowers._getQuotaManager().persisted(principal);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function estimateOrigin(principal, callback) {
|
|
let request = SpecialPowers._getQuotaManager().estimate(principal);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function listOrigins(callback) {
|
|
let request = SpecialPowers._getQuotaManager().listOrigins(callback);
|
|
request.callback = callback;
|
|
|
|
return request;
|
|
}
|
|
|
|
function getPersistedFromMetadata(readBuffer) {
|
|
const persistedPosition = 8; // Persisted state is stored in the 9th byte
|
|
let view =
|
|
readBuffer instanceof Uint8Array ? readBuffer : new Uint8Array(readBuffer);
|
|
|
|
return !!view[persistedPosition];
|
|
}
|
|
|
|
function grabResultAndContinueHandler(request) {
|
|
testGenerator.next(request.result);
|
|
}
|
|
|
|
function grabUsageAndContinueHandler(request) {
|
|
testGenerator.next(request.result.usage);
|
|
}
|
|
|
|
function getUsage(usageHandler, getAll) {
|
|
let request = SpecialPowers._getQuotaManager().getUsage(usageHandler, getAll);
|
|
|
|
return request;
|
|
}
|
|
|
|
function getOriginUsage(principal) {
|
|
let request = Services.qms.getUsageForPrincipal(principal, function () {});
|
|
|
|
return request;
|
|
}
|
|
|
|
function getCachedOriginUsage(principal) {
|
|
let request = Services.qms.getCachedUsageForPrincipal(
|
|
principal,
|
|
function () {}
|
|
);
|
|
|
|
return request;
|
|
}
|
|
|
|
function getCachedOriginUsage(principal) {
|
|
let request = Services.qms.getCachedUsageForPrincipal(principal);
|
|
|
|
return request;
|
|
}
|
|
|
|
function getCurrentUsage(usageHandler) {
|
|
let principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(
|
|
Ci.nsIPrincipal
|
|
);
|
|
let request = SpecialPowers._getQuotaManager().getUsageForPrincipal(
|
|
principal,
|
|
usageHandler
|
|
);
|
|
|
|
return request;
|
|
}
|
|
|
|
function getPrincipal(url, attr = {}) {
|
|
let uri = Cc["@mozilla.org/network/io-service;1"]
|
|
.getService(Ci.nsIIOService)
|
|
.newURI(url);
|
|
let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
|
|
Ci.nsIScriptSecurityManager
|
|
);
|
|
return ssm.createContentPrincipal(uri, attr);
|
|
}
|
|
|
|
var SpecialPowers = {
|
|
getBoolPref(prefName) {
|
|
return this._getPrefs().getBoolPref(prefName);
|
|
},
|
|
|
|
setBoolPref(prefName, value) {
|
|
this._getPrefs().setBoolPref(prefName, value);
|
|
},
|
|
|
|
setIntPref(prefName, value) {
|
|
this._getPrefs().setIntPref(prefName, value);
|
|
},
|
|
|
|
clearUserPref(prefName) {
|
|
this._getPrefs().clearUserPref(prefName);
|
|
},
|
|
|
|
_getPrefs() {
|
|
let prefService = Cc["@mozilla.org/preferences-service;1"].getService(
|
|
Ci.nsIPrefService
|
|
);
|
|
return prefService.getBranch(null);
|
|
},
|
|
|
|
_getQuotaManager() {
|
|
return Cc["@mozilla.org/dom/quota-manager-service;1"].getService(
|
|
Ci.nsIQuotaManagerService
|
|
);
|
|
},
|
|
};
|
|
|
|
function installPackages(packageRelativePaths) {
|
|
if (packageRelativePaths.length != 2) {
|
|
throw new Error("Unsupported number of package relative paths");
|
|
}
|
|
|
|
for (const packageRelativePath of packageRelativePaths) {
|
|
installPackage(packageRelativePath);
|
|
}
|
|
}
|
|
|
|
// Take current storage structure on disk and compare it with the expected
|
|
// structure. The expected structure is defined in JSON and consists of a per
|
|
// test package definition and a shared package definition. The shared package
|
|
// definition should contain unknown stuff which needs to be properly handled
|
|
// in all situations.
|
|
function verifyStorage(packageDefinitionRelativePaths, key, sharedKey) {
|
|
if (packageDefinitionRelativePaths.length != 2) {
|
|
throw new Error("Unsupported number of package definition relative paths");
|
|
}
|
|
|
|
function verifyEntries(entries, name, indent = "") {
|
|
log(`${indent}Verifying ${name} entries`);
|
|
|
|
indent += " ";
|
|
|
|
for (const entry of entries) {
|
|
const maybeName = entry.name;
|
|
|
|
log(`${indent}Verifying entry ${maybeName}`);
|
|
|
|
let hasName = false;
|
|
let hasDir = false;
|
|
let hasEntries = false;
|
|
|
|
for (const property in entry) {
|
|
switch (property) {
|
|
case "note":
|
|
case "todo":
|
|
break;
|
|
|
|
case "name":
|
|
hasName = true;
|
|
break;
|
|
|
|
case "dir":
|
|
hasDir = true;
|
|
break;
|
|
|
|
case "entries":
|
|
hasEntries = true;
|
|
break;
|
|
|
|
default:
|
|
throw new Error(`Unknown property ${property}`);
|
|
}
|
|
}
|
|
|
|
if (!hasName) {
|
|
throw new Error("An entry must have the name property");
|
|
}
|
|
|
|
if (!hasDir) {
|
|
throw new Error("An entry must have the dir property");
|
|
}
|
|
|
|
if (hasEntries && !entry.dir) {
|
|
throw new Error("An entry can't have entries if it's not a directory");
|
|
}
|
|
|
|
if (hasEntries) {
|
|
verifyEntries(entry.entries, entry.name, indent);
|
|
}
|
|
}
|
|
}
|
|
|
|
function getCurrentEntries() {
|
|
log("Getting current entries");
|
|
|
|
function getEntryForFile(file) {
|
|
let entry = {
|
|
name: file.leafName,
|
|
dir: file.isDirectory(),
|
|
};
|
|
|
|
if (file.isDirectory()) {
|
|
const enumerator = file.directoryEntries;
|
|
let nextFile;
|
|
while ((nextFile = enumerator.nextFile)) {
|
|
if (!entry.entries) {
|
|
entry.entries = [];
|
|
}
|
|
entry.entries.push(getEntryForFile(nextFile));
|
|
}
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
let entries = [];
|
|
|
|
let file = getRelativeFile("indexedDB");
|
|
if (file.exists()) {
|
|
entries.push(getEntryForFile(file));
|
|
}
|
|
|
|
file = getRelativeFile("storage");
|
|
if (file.exists()) {
|
|
entries.push(getEntryForFile(file));
|
|
}
|
|
|
|
file = getRelativeFile("storage.sqlite");
|
|
if (file.exists()) {
|
|
entries.push(getEntryForFile(file));
|
|
}
|
|
|
|
verifyEntries(entries, "current");
|
|
|
|
return entries;
|
|
}
|
|
|
|
function getEntriesFromPackageDefinition(
|
|
packageDefinitionRelativePath,
|
|
lookupKey
|
|
) {
|
|
log(`Getting ${lookupKey} entries from ${packageDefinitionRelativePath}`);
|
|
|
|
const currentDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
|
|
const file = getRelativeFile(
|
|
packageDefinitionRelativePath + ".json",
|
|
currentDir
|
|
);
|
|
|
|
const fileInputStream = Cc[
|
|
"@mozilla.org/network/file-input-stream;1"
|
|
].createInstance(Ci.nsIFileInputStream);
|
|
fileInputStream.init(file, -1, -1, 0);
|
|
|
|
const scriptableInputStream = Cc[
|
|
"@mozilla.org/scriptableinputstream;1"
|
|
].createInstance(Ci.nsIScriptableInputStream);
|
|
scriptableInputStream.init(fileInputStream);
|
|
|
|
const data = scriptableInputStream.readBytes(
|
|
scriptableInputStream.available()
|
|
);
|
|
|
|
const obj = JSON.parse(data);
|
|
|
|
const result = obj.find(({ key: elementKey }) => elementKey == lookupKey);
|
|
|
|
if (!result) {
|
|
throw new Error("The file doesn't contain an element for given key");
|
|
}
|
|
|
|
if (!result.entries) {
|
|
throw new Error("The element doesn't have the entries property");
|
|
}
|
|
|
|
verifyEntries(result.entries, lookupKey);
|
|
|
|
return result.entries;
|
|
}
|
|
|
|
function addSharedEntries(expectedEntries, sharedEntries, name, indent = "") {
|
|
log(`${indent}Checking common ${name} entries`);
|
|
|
|
indent += " ";
|
|
|
|
for (const sharedEntry of sharedEntries) {
|
|
const expectedEntry = expectedEntries.find(
|
|
({ name: elementName }) => elementName == sharedEntry.name
|
|
);
|
|
|
|
if (expectedEntry) {
|
|
log(`${indent}Checking common entry ${sharedEntry.name}`);
|
|
|
|
if (!expectedEntry.dir || !sharedEntry.dir) {
|
|
throw new Error("A common entry must be a directory");
|
|
}
|
|
|
|
if (!expectedEntry.entries && !sharedEntry.entries) {
|
|
throw new Error("A common entry must not be a leaf");
|
|
}
|
|
|
|
if (sharedEntry.entries) {
|
|
if (!expectedEntry.entries) {
|
|
expectedEntry.entries = [];
|
|
}
|
|
|
|
addSharedEntries(
|
|
expectedEntry.entries,
|
|
sharedEntry.entries,
|
|
sharedEntry.name,
|
|
indent
|
|
);
|
|
}
|
|
} else {
|
|
log(`${indent}Adding entry ${sharedEntry.name}`);
|
|
expectedEntries.push(sharedEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
function compareEntries(currentEntries, expectedEntries, name, indent = "") {
|
|
log(`${indent}Comparing ${name} entries`);
|
|
|
|
indent += " ";
|
|
|
|
if (currentEntries.length != expectedEntries.length) {
|
|
throw new Error("Entries must have the same length");
|
|
}
|
|
|
|
for (const currentEntry of currentEntries) {
|
|
log(`${indent}Comparing entry ${currentEntry.name}`);
|
|
|
|
const expectedEntry = expectedEntries.find(
|
|
({ name: elementName }) => elementName == currentEntry.name
|
|
);
|
|
|
|
if (!expectedEntry) {
|
|
throw new Error("Cannot find a matching entry");
|
|
}
|
|
|
|
if (expectedEntry.dir != currentEntry.dir) {
|
|
throw new Error("The dir property doesn't match");
|
|
}
|
|
|
|
if (
|
|
(expectedEntry.entries && !currentEntry.entries) ||
|
|
(!expectedEntry.entries && currentEntry.entries)
|
|
) {
|
|
throw new Error("The entries property doesn't match");
|
|
}
|
|
|
|
if (expectedEntry.entries) {
|
|
compareEntries(
|
|
currentEntry.entries,
|
|
expectedEntry.entries,
|
|
currentEntry.name,
|
|
indent
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
const currentEntries = getCurrentEntries();
|
|
|
|
log("Stringified current entries: " + JSON.stringify(currentEntries));
|
|
|
|
const expectedEntries = getEntriesFromPackageDefinition(
|
|
packageDefinitionRelativePaths[0],
|
|
key
|
|
);
|
|
const sharedEntries = getEntriesFromPackageDefinition(
|
|
packageDefinitionRelativePaths[1],
|
|
sharedKey ? sharedKey : key
|
|
);
|
|
|
|
addSharedEntries(expectedEntries, sharedEntries, key);
|
|
|
|
log("Stringified expected entries: " + JSON.stringify(expectedEntries));
|
|
|
|
compareEntries(currentEntries, expectedEntries, key);
|
|
}
|
|
|
|
async function verifyInitializationStatus(
|
|
expectStorageIsInitialized,
|
|
expectPersistentStorageIsInitialized,
|
|
expectTemporaryStorageIsInitialized
|
|
) {
|
|
if (!expectStorageIsInitialized && expectPersistentStorageIsInitialized) {
|
|
throw new Error("Invalid expectation");
|
|
}
|
|
|
|
if (!expectStorageIsInitialized && expectTemporaryStorageIsInitialized) {
|
|
throw new Error("Invalid expectation");
|
|
}
|
|
|
|
let request = storageInitialized();
|
|
await requestFinished(request);
|
|
|
|
const storageIsInitialized = request.result;
|
|
|
|
request = persistentStorageInitialized();
|
|
await requestFinished(request);
|
|
|
|
const persistentStorageIsInitialized = request.result;
|
|
|
|
request = temporaryStorageInitialized();
|
|
await requestFinished(request);
|
|
|
|
const temporaryStorageIsInitialized = request.result;
|
|
|
|
ok(
|
|
!(!storageIsInitialized && persistentStorageIsInitialized),
|
|
"Initialization status is consistent"
|
|
);
|
|
|
|
ok(
|
|
!(!storageIsInitialized && temporaryStorageIsInitialized),
|
|
"Initialization status is consistent"
|
|
);
|
|
|
|
if (expectStorageIsInitialized) {
|
|
ok(storageIsInitialized, "Storage is initialized");
|
|
} else {
|
|
ok(!storageIsInitialized, "Storage is not initialized");
|
|
}
|
|
|
|
if (expectPersistentStorageIsInitialized) {
|
|
ok(persistentStorageIsInitialized, "Persistent storage is initialized");
|
|
} else {
|
|
ok(
|
|
!persistentStorageIsInitialized,
|
|
"Persistent storage is not initialized"
|
|
);
|
|
}
|
|
|
|
if (expectTemporaryStorageIsInitialized) {
|
|
ok(temporaryStorageIsInitialized, "Temporary storage is initialized");
|
|
} else {
|
|
ok(!temporaryStorageIsInitialized, "Temporary storage is not initialized");
|
|
}
|
|
}
|