diff options
Diffstat (limited to 'toolkit/components/cleardata/tests/unit/test_quota.js')
-rw-r--r-- | toolkit/components/cleardata/tests/unit/test_quota.js | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/toolkit/components/cleardata/tests/unit/test_quota.js b/toolkit/components/cleardata/tests/unit/test_quota.js new file mode 100644 index 0000000000..95c3025781 --- /dev/null +++ b/toolkit/components/cleardata/tests/unit/test_quota.js @@ -0,0 +1,537 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests for the QuotaCleaner. + */ + +"use strict"; + +// The following tests ensure we properly clear (partitioned/unpartitioned) +// localStorage and indexedDB when using deleteDataFromBaseDomain, +// deleteDataFromHost and deleteDataFromPrincipal. + +// Skip localStorage tests when using legacy localStorage. The legacy +// localStorage implementation does not support clearing data by principal. See +// Bug 1688221, Bug 1688665. +const skipLocalStorageTests = Services.prefs.getBoolPref( + "dom.storage.enable_unsupported_legacy_implementation" +); + +/** + * Create an origin with partitionKey. + * @param {String} host - Host portion of origin to create. + * @param {String} [topLevelBaseDomain] - Optional first party base domain to use for partitionKey. + * @param {Object} [originAttributes] - Optional object of origin attributes to + * set. If topLevelBaseDomain is passed, the partitionKey will be overwritten. + * @returns {String} Origin with suffix. + */ +function getOrigin(host, topLevelBaseDomain, originAttributes = {}) { + return getPrincipal(host, topLevelBaseDomain, originAttributes).origin; +} + +function getPrincipal(host, topLevelBaseDomain, originAttributes = {}) { + originAttributes = getOAWithPartitionKey( + { topLevelBaseDomain }, + originAttributes + ); + let principal = Services.scriptSecurityManager.createContentPrincipal( + Services.io.newURI(`https://${host}`), + originAttributes + ); + return principal; +} + +function getTestEntryName(host, topLevelBaseDomain) { + if (!topLevelBaseDomain) { + return host; + } + return `${host}_${topLevelBaseDomain}`; +} + +function setTestEntry({ + storageType, + host, + topLevelBaseDomain = null, + originAttributes = {}, +}) { + let origin = getOrigin(host, topLevelBaseDomain, originAttributes); + if (storageType == "localStorage") { + SiteDataTestUtils.addToLocalStorage( + origin, + getTestEntryName(host, topLevelBaseDomain), + "bar" + ); + return; + } + SiteDataTestUtils.addToIndexedDB(origin); +} + +async function testEntryExists({ + storageType, + host, + topLevelBaseDomain = null, + expected = true, + originAttributes = {}, +}) { + let exists; + let origin = getOrigin(host, topLevelBaseDomain, originAttributes); + if (storageType == "localStorage") { + exists = SiteDataTestUtils.hasLocalStorage(origin, [ + { key: getTestEntryName(host, topLevelBaseDomain), value: "bar" }, + ]); + } else { + exists = await SiteDataTestUtils.hasIndexedDB(origin); + } + + let message = `${storageType} entry ${ + expected ? "is set" : "is not set" + } for ${host}`; + if (topLevelBaseDomain) { + message += ` partitioned under ${topLevelBaseDomain}`; + } + Assert.equal(exists, expected, message); + return exists; +} + +const TEST_ORIGINS = [ + // First party + { host: "example.net" }, + { host: "test.example.net" }, + { host: "example.org" }, + + // Third-party partitioned. + { host: "example.com", topLevelBaseDomain: "example.net" }, + { + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + }, + { host: "example.net", topLevelBaseDomain: "example.org" }, + { host: "test.example.net", topLevelBaseDomain: "example.org" }, +]; + +async function setTestEntries(storageType) { + for (const origin of TEST_ORIGINS) { + setTestEntry({ storageType, ...origin }); + } + + // Ensure we have the correct storage test state. + for (const origin of TEST_ORIGINS) { + await testEntryExists({ storageType, ...origin }); + } +} + +/** + * Run the base domain test with either localStorage or indexedDB. + * @param {('localStorage'|'indexedDB')} storageType + */ +async function runTestBaseDomain(storageType) { + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + await setTestEntries(storageType); + + // Clear entries of example.net including partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromBaseDomain( + "example.net", + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + await testEntryExists({ storageType, host: "example.net", expected: false }); + await testEntryExists({ + storageType, + host: "test.example.net", + expected: false, + }); + await testEntryExists({ storageType, host: "example.org" }); + + await testEntryExists({ + storageType, + host: "example.com", + topLevelBaseDomain: "example.net", + expected: false, + }); + await testEntryExists({ + storageType, + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + expected: false, + }); + await testEntryExists({ + storageType, + host: "example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + await testEntryExists({ + storageType, + host: "test.example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + + // Cleanup + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); +} + +/** + * Run the host test with either localStorage or indexedDB. + * @param {('localStorage'|'indexedDB')} storageType + */ +async function runTestHost(storageType) { + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + await setTestEntries(storageType); + + // Clear entries of example.net without partitions. + await new Promise(aResolve => { + Services.clearData.deleteDataFromHost( + "example.net", + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + await testEntryExists({ storageType, host: "example.net", expected: false }); + // QuotaCleaner#deleteByHost also clears subdomains. + await testEntryExists({ + storageType, + host: "test.example.net", + expected: false, + }); + await testEntryExists({ storageType, host: "example.org" }); + + await testEntryExists({ + storageType, + host: "example.com", + topLevelBaseDomain: "example.net", + expected: true, + }); + await testEntryExists({ + storageType, + host: "example.com", + topLevelBaseDomain: "example.net", + originAttributes: { userContextId: 1 }, + expected: true, + }); + // QuotaCleaner#deleteByHost ignores partitionKey. + await testEntryExists({ + storageType, + host: "example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + await testEntryExists({ + storageType, + host: "test.example.net", + topLevelBaseDomain: "example.org", + expected: false, + }); + + // Cleanup + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); +} + +/** + * Run the principal test with either localStorage or indexedDB. + * @param {('localStorage'|'indexedDB')} storageType + */ +async function runTestPrincipal(storageType) { + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + // First party + setTestEntry({ storageType, host: "example.net" }); + setTestEntry({ + storageType, + host: "example.net", + originAttributes: { userContextId: 2 }, + }); + setTestEntry({ + storageType, + host: "example.net", + originAttributes: { privateBrowsingId: 1 }, + }); + setTestEntry({ storageType, host: "test.example.net" }); + setTestEntry({ storageType, host: "example.org" }); + + // Third-party partitioned. + setTestEntry({ + storageType, + host: "example.net", + topLevelBaseDomain: "example.com", + }); + + // Ensure we have the correct storage test state. + await testEntryExists({ storageType, host: "example.net" }); + await testEntryExists({ + storageType, + host: "example.net", + originAttributes: { userContextId: 2 }, + }); + await testEntryExists({ + storageType, + host: "example.net", + originAttributes: { privateBrowsingId: 1 }, + }); + await testEntryExists({ storageType, host: "test.example.net" }); + await testEntryExists({ storageType, host: "example.org" }); + await testEntryExists({ + storageType, + host: "example.net", + topLevelBaseDomain: "example.com", + }); + + // Clear entries from principal with custom OA. + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + getPrincipal("example.net", null, { userContextId: 2 }), + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + // Test that we only deleted entries for the exact origin. + await testEntryExists({ storageType, host: "example.net" }); + await testEntryExists({ + expected: false, + storageType, + host: "example.net", + originAttributes: { userContextId: 2 }, + }); + await testEntryExists({ + storageType, + host: "example.net", + originAttributes: { privateBrowsingId: 1 }, + }); + await testEntryExists({ storageType, host: "test.example.net" }); + await testEntryExists({ storageType, host: "example.org" }); + await testEntryExists({ + storageType, + host: "example.net", + topLevelBaseDomain: "example.com", + }); + + // Clear entries of from partitioned principal. + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + getPrincipal("example.net", "example.com"), + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + // Test that we only deleted entries for the partition. + await testEntryExists({ storageType, host: "example.net" }); + await testEntryExists({ + expected: false, + storageType, + host: "example.net", + originAttributes: { userContextId: 2 }, + }); + await testEntryExists({ + storageType, + host: "example.net", + originAttributes: { privateBrowsingId: 1 }, + }); + await testEntryExists({ storageType, host: "test.example.net" }); + await testEntryExists({ storageType, host: "example.org" }); + await testEntryExists({ + expected: false, + storageType, + host: "example.net", + topLevelBaseDomain: "example.com", + }); + + // Clear entries of from principal without suffix. + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + getPrincipal("example.net", null), + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + // Test that we only deleted entries for the given principal, and not entries + // for principals with the same host, but different OriginAttributes or + // subdomains. + await testEntryExists({ expected: false, storageType, host: "example.net" }); + await testEntryExists({ + expected: false, + storageType, + host: "example.net", + originAttributes: { userContextId: 2 }, + }); + await testEntryExists({ + storageType, + host: "example.net", + originAttributes: { privateBrowsingId: 1 }, + }); + + await testEntryExists({ storageType, host: "test.example.net" }); + await testEntryExists({ storageType, host: "example.org" }); + await testEntryExists({ + expected: false, + storageType, + host: "example.net", + topLevelBaseDomain: "example.com", + }); + + // Cleanup + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); +} + +// Tests + +add_task(function setup() { + // Allow setting local storage in xpcshell tests. + Services.prefs.setBoolPref("dom.storage.client_validation", false); +}); + +/** + * Tests deleting localStorage entries by host. + */ +add_task(async function test_host_localStorage() { + await runTestHost("localStorage"); +}); + +/** + * Tests deleting indexedDB entries by host. + */ +add_task(async function test_host_indexedDB() { + await runTestHost("indexedDB"); +}); + +/** + * Tests deleting (partitioned) localStorage entries by base domain. + */ +add_task(async function test_baseDomain_localStorage() { + await runTestBaseDomain("localStorage"); +}); + +/** + * Tests deleting (partitioned) indexedDB entries by base domain. + */ +add_task(async function test_baseDomain_indexedDB() { + await runTestBaseDomain("indexedDB"); +}); + +/** + * Tests deleting localStorage entries by principal. + */ +add_task(async function test_principal_localStorage() { + // Bug 1688221, Bug 1688665. + if (skipLocalStorageTests) { + info("Skipping test"); + return; + } + await runTestPrincipal("localStorage"); +}); + +function getRelativeFile(...components) { + const profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile); + + const file = profileDir.clone(); + for (const component of components) { + file.append(component); + } + + return file; +} + +function countSubitems(file) { + const entriesIterator = file.directoryEntries; + let count = 0; + while (entriesIterator.hasMoreElements()) { + ++count; + entriesIterator.nextFile; + } + return count; +} + +add_task(async function test_deleteAllAtShutdown() { + const storageType = "indexedDB"; + + await new Promise(aResolve => { + Services.clearData.deleteData( + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + const toBeRemovedDir = getRelativeFile("storage", "to-be-removed"); + if (toBeRemovedDir.exists()) { + toBeRemovedDir.remove(true); + } + + await setTestEntries(storageType); + + Services.startup.advanceShutdownPhase( + Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNTEARDOWN + ); + + // Clear entries from principal with custom OA. + for (const origin of TEST_ORIGINS) { + await new Promise(aResolve => { + Services.clearData.deleteDataFromPrincipal( + getPrincipal( + origin.host, + origin.topLevelBaseDomain, + origin.originAttributes + ), + false, + Ci.nsIClearDataService.CLEAR_DOM_QUOTA, + aResolve + ); + }); + + await testEntryExists({ expected: false, storageType, ...origin }); + } + + Assert.ok( + toBeRemovedDir.exists(), + "to-be-removed directory should exist now" + ); + + Assert.equal( + countSubitems(toBeRemovedDir), + TEST_ORIGINS.length, + `storage/to-be-removed has ${TEST_ORIGINS.length} subdirectories` + ); +}); |