diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/quota/test/xpcshell/common | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/quota/test/xpcshell/common')
-rw-r--r-- | dom/quota/test/xpcshell/common/head.js | 636 | ||||
-rw-r--r-- | dom/quota/test/xpcshell/common/utils.js | 47 |
2 files changed, 683 insertions, 0 deletions
diff --git a/dom/quota/test/xpcshell/common/head.js b/dom/quota/test/xpcshell/common/head.js new file mode 100644 index 0000000000..b1cd11b4ed --- /dev/null +++ b/dom/quota/test/xpcshell/common/head.js @@ -0,0 +1,636 @@ +/** + * 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(); + + Cu.importGlobalProperties(["indexedDB", "File", "Blob", "FileReader"]); + + // 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 temporaryStorageInitialized(callback) { + let request = SpecialPowers._getQuotaManager().temporaryStorageInitialized(); + request.callback = callback; + + return request; +} + +function init(callback) { + let request = SpecialPowers._getQuotaManager().init(); + 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, callback) { + let request = SpecialPowers._getQuotaManager().initializeTemporaryOrigin( + persistence, + principal + ); + 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, persistence, client, callback) { + let request = SpecialPowers._getQuotaManager().clearStoragesForPrincipal( + principal, + persistence, + client + ); + request.callback = callback; + + return request; +} + +function clearOrigin(principal, persistence, callback) { + let request = SpecialPowers._getQuotaManager().clearStoragesForPrincipal( + 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.resetStoragesForPrincipal( + principal, + "default", + client + ); + + 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, fromMemory = false) { + let request = Services.qms.getUsageForPrincipal( + principal, + function () {}, + fromMemory + ); + + 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) { + 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], + key + ); + + addSharedEntries(expectedEntries, sharedEntries, key); + + log("Stringified expected entries: " + JSON.stringify(expectedEntries)); + + compareEntries(currentEntries, expectedEntries, key); +} + +async function verifyInitializationStatus( + expectStorageIsInitialized, + expectTemporaryStorageIsInitialized +) { + if (!expectStorageIsInitialized && expectTemporaryStorageIsInitialized) { + throw new Error("Invalid expectation"); + } + + let request = storageInitialized(); + await requestFinished(request); + + const storageIsInitialized = request.result; + + request = temporaryStorageInitialized(); + await requestFinished(request); + + const temporaryStorageIsInitialized = request.result; + + ok( + !(!storageIsInitialized && temporaryStorageIsInitialized), + "Initialization status is consistent" + ); + + if (expectStorageIsInitialized) { + ok(storageIsInitialized, "Storage is initialized"); + } else { + ok(!storageIsInitialized, "Storage is not initialized"); + } + + if (expectTemporaryStorageIsInitialized) { + ok(temporaryStorageIsInitialized, "Temporary storage is initialized"); + } else { + ok(!temporaryStorageIsInitialized, "Temporary storage is not initialized"); + } +} diff --git a/dom/quota/test/xpcshell/common/utils.js b/dom/quota/test/xpcshell/common/utils.js new file mode 100644 index 0000000000..ee21c90cf2 --- /dev/null +++ b/dom/quota/test/xpcshell/common/utils.js @@ -0,0 +1,47 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +loadScript("dom/quota/test/common/file.js"); + +function getOriginDir(persistence, origin) { + return getRelativeFile(`storage/${persistence}/${origin}`); +} + +function getMetadataFile(persistence, origin) { + const metadataFile = getOriginDir(persistence, origin); + metadataFile.append(".metadata-v2"); + return metadataFile; +} + +function populateRepository(persistence) { + const originDir = getOriginDir(persistence, "https+++good-example.com"); + originDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); +} + +function makeRepositoryUnusable(persistence) { + // For the purpose of testing, we make a repository unusable by creating an + // origin directory with the metadata file created as a directory (not a + // file). + const metadataFile = getMetadataFile(persistence, "https+++bad-example.com"); + metadataFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); +} + +async function fillOrigin(principal, size) { + let database = getSimpleDatabase(principal); + + let request = database.open("data"); + await requestFinished(request); + + try { + request = database.write(getBuffer(size)); + await requestFinished(request); + ok(true, "Should not have thrown"); + } catch (ex) { + ok(false, "Should not have thrown"); + } + + request = database.close(); + await requestFinished(request); +} |