summaryrefslogtreecommitdiffstats
path: root/dom/quota/test
diff options
context:
space:
mode:
Diffstat (limited to 'dom/quota/test')
-rw-r--r--dom/quota/test/browser/browser.ini13
-rw-r--r--dom/quota/test/browser/browser_permissionsCrossOrigin.js56
-rw-r--r--dom/quota/test/browser/browser_permissionsPromptAllow.js66
-rw-r--r--dom/quota/test/browser/browser_permissionsPromptDeny.js150
-rw-r--r--dom/quota/test/browser/browser_permissionsPromptUnknown.js53
-rw-r--r--dom/quota/test/browser/browser_simpledb.js51
-rw-r--r--dom/quota/test/browser/empty.html8
-rw-r--r--dom/quota/test/browser/head.js149
-rw-r--r--dom/quota/test/browser/helpers.js46
-rw-r--r--dom/quota/test/browser/permissionsPrompt.html34
-rw-r--r--dom/quota/test/common/browser.js34
-rw-r--r--dom/quota/test/common/content.js62
-rw-r--r--dom/quota/test/common/file.js45
-rw-r--r--dom/quota/test/common/global.js47
-rw-r--r--dom/quota/test/common/mochitest.js19
-rw-r--r--dom/quota/test/common/nestedtest.js6
-rw-r--r--dom/quota/test/common/system.js74
-rw-r--r--dom/quota/test/common/test_simpledb.js50
-rw-r--r--dom/quota/test/common/test_storage_manager_persist_allow.js30
-rw-r--r--dom/quota/test/common/test_storage_manager_persist_deny.js34
-rw-r--r--dom/quota/test/common/test_storage_manager_persisted.js13
-rw-r--r--dom/quota/test/common/xpcshell.js91
-rw-r--r--dom/quota/test/gtest/Common.cpp28
-rw-r--r--dom/quota/test/gtest/Common.h32
-rw-r--r--dom/quota/test/gtest/PQuotaTest.ipdl26
-rw-r--r--dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp160
-rw-r--r--dom/quota/test/gtest/QuotaManagerDependencyFixture.h87
-rw-r--r--dom/quota/test/gtest/QuotaTestChild.h23
-rw-r--r--dom/quota/test/gtest/QuotaTestParent.h36
-rw-r--r--dom/quota/test/gtest/TestCheckedUnsafePtr.cpp145
-rw-r--r--dom/quota/test/gtest/TestClientUsageArray.cpp17
-rw-r--r--dom/quota/test/gtest/TestEncryptedStream.cpp791
-rw-r--r--dom/quota/test/gtest/TestFileOutputStream.cpp185
-rw-r--r--dom/quota/test/gtest/TestFlatten.cpp81
-rw-r--r--dom/quota/test/gtest/TestForwardDecls.cpp12
-rw-r--r--dom/quota/test/gtest/TestPersistenceType.cpp44
-rw-r--r--dom/quota/test/gtest/TestQMResult.cpp72
-rw-r--r--dom/quota/test/gtest/TestQuotaCommon.cpp2171
-rw-r--r--dom/quota/test/gtest/TestQuotaManager.cpp181
-rw-r--r--dom/quota/test/gtest/TestResultExtensions.cpp333
-rw-r--r--dom/quota/test/gtest/TestScopedLogExtraInfo.cpp65
-rw-r--r--dom/quota/test/gtest/TestUsageInfo.cpp136
-rw-r--r--dom/quota/test/gtest/moz.build44
-rw-r--r--dom/quota/test/mochitest/helpers.js312
-rw-r--r--dom/quota/test/mochitest/mochitest.ini18
-rw-r--r--dom/quota/test/mochitest/test_simpledb.html21
-rw-r--r--dom/quota/test/mochitest/test_storage_manager_persist_allow.html21
-rw-r--r--dom/quota/test/mochitest/test_storage_manager_persist_deny.html21
-rw-r--r--dom/quota/test/mochitest/test_storage_manager_persisted.html24
-rw-r--r--dom/quota/test/modules/content/.eslintrc.js24
-rw-r--r--dom/quota/test/modules/content/Assert.js10
-rw-r--r--dom/quota/test/modules/content/ModuleLoader.js61
-rw-r--r--dom/quota/test/modules/content/StorageUtils.js67
-rw-r--r--dom/quota/test/modules/content/Utils.js14
-rw-r--r--dom/quota/test/modules/content/UtilsParent.js21
-rw-r--r--dom/quota/test/modules/content/WorkerDriver.js68
-rw-r--r--dom/quota/test/modules/content/worker/.eslintrc.js21
-rw-r--r--dom/quota/test/modules/content/worker/Assert.js22
-rw-r--r--dom/quota/test/modules/content/worker/ModuleLoader.js52
-rw-r--r--dom/quota/test/modules/content/worker/Utils.js27
-rw-r--r--dom/quota/test/modules/content/worker/UtilsChild.mjs22
-rw-r--r--dom/quota/test/modules/content/worker/head.js55
-rw-r--r--dom/quota/test/modules/system/ModuleLoader.sys.mjs63
-rw-r--r--dom/quota/test/modules/system/StorageUtils.sys.mjs101
-rw-r--r--dom/quota/test/modules/system/Utils.sys.mjs38
-rw-r--r--dom/quota/test/modules/system/UtilsParent.sys.mjs32
-rw-r--r--dom/quota/test/modules/system/WorkerDriver.sys.mjs68
-rw-r--r--dom/quota/test/modules/system/worker/.eslintrc.js21
-rw-r--r--dom/quota/test/modules/system/worker/Assert.js22
-rw-r--r--dom/quota/test/modules/system/worker/ModuleLoader.js52
-rw-r--r--dom/quota/test/modules/system/worker/Utils.js55
-rw-r--r--dom/quota/test/modules/system/worker/UtilsChild.js56
-rw-r--r--dom/quota/test/modules/system/worker/head.js56
-rw-r--r--dom/quota/test/moz.build84
-rw-r--r--dom/quota/test/xpcshell/basics_profile.zipbin0 -> 3405 bytes
-rw-r--r--dom/quota/test/xpcshell/caching/groupMismatch_profile.zipbin0 -> 2576 bytes
-rw-r--r--dom/quota/test/xpcshell/caching/head.js14
-rw-r--r--dom/quota/test/xpcshell/caching/make_unsetLastAccessTime.js25
-rw-r--r--dom/quota/test/xpcshell/caching/removedOrigin_profile.zipbin0 -> 1551 bytes
-rw-r--r--dom/quota/test/xpcshell/caching/test_groupMismatch.js45
-rw-r--r--dom/quota/test/xpcshell/caching/test_removedOrigin.js61
-rw-r--r--dom/quota/test/xpcshell/caching/test_unsetLastAccessTime.js46
-rw-r--r--dom/quota/test/xpcshell/caching/unsetLastAccessTime_profile.zipbin0 -> 1574 bytes
-rw-r--r--dom/quota/test/xpcshell/caching/xpcshell.ini17
-rw-r--r--dom/quota/test/xpcshell/clearStoragesForPrincipal_profile.zipbin0 -> 7380 bytes
-rw-r--r--dom/quota/test/xpcshell/clearStoragesForPrivateBrowsing_profile.json152
-rw-r--r--dom/quota/test/xpcshell/clearStoragesForPrivateBrowsing_profile.zipbin0 -> 7500 bytes
-rw-r--r--dom/quota/test/xpcshell/common/head.js636
-rw-r--r--dom/quota/test/xpcshell/common/utils.js47
-rw-r--r--dom/quota/test/xpcshell/createLocalStorage_profile.zipbin0 -> 1006 bytes
-rw-r--r--dom/quota/test/xpcshell/defaultStorageDirectory_shared.json141
-rw-r--r--dom/quota/test/xpcshell/defaultStorageDirectory_shared.zipbin0 -> 1804 bytes
-rw-r--r--dom/quota/test/xpcshell/getUsage_profile.zipbin0 -> 24717 bytes
-rw-r--r--dom/quota/test/xpcshell/groupMismatch_profile.zipbin0 -> 1706 bytes
-rw-r--r--dom/quota/test/xpcshell/head.js14
-rw-r--r--dom/quota/test/xpcshell/indexedDBDirectory_shared.json35
-rw-r--r--dom/quota/test/xpcshell/indexedDBDirectory_shared.zipbin0 -> 524 bytes
-rw-r--r--dom/quota/test/xpcshell/make_unknownFiles.js172
-rw-r--r--dom/quota/test/xpcshell/make_unsetLastAccessTime.js25
-rw-r--r--dom/quota/test/xpcshell/originMismatch_profile.json77
-rw-r--r--dom/quota/test/xpcshell/originMismatch_profile.zipbin0 -> 3104 bytes
-rw-r--r--dom/quota/test/xpcshell/persistentStorageDirectory_shared.json57
-rw-r--r--dom/quota/test/xpcshell/persistentStorageDirectory_shared.zipbin0 -> 1272 bytes
-rw-r--r--dom/quota/test/xpcshell/removeLocalStorage1_profile.zipbin0 -> 1187 bytes
-rw-r--r--dom/quota/test/xpcshell/removeLocalStorage2_profile.zipbin0 -> 2827 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/head.js14
-rw-r--r--dom/quota/test/xpcshell/telemetry/test_qm_first_initialization_attempt.js866
-rw-r--r--dom/quota/test/xpcshell/telemetry/version0_0_make_it_unusable.zipbin0 -> 1978 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/version0_0_profile.zipbin0 -> 1292 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/version1_0_make_it_unusable.zipbin0 -> 1978 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/version1_0_profile.zipbin0 -> 1475 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/version2_0_make_it_unusable.zipbin0 -> 1978 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/version2_0_profile.zipbin0 -> 1473 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/version2_1_make_it_unusable.zipbin0 -> 1978 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/version2_1_profile.zipbin0 -> 1472 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/version2_2_make_it_unusable.zipbin0 -> 299 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/version2_2_profile.zipbin0 -> 1507 bytes
-rw-r--r--dom/quota/test/xpcshell/telemetry/xpcshell.ini20
-rw-r--r--dom/quota/test/xpcshell/tempMetadataCleanup_profile.zipbin0 -> 1569 bytes
-rw-r--r--dom/quota/test/xpcshell/test_allowListFiles.js61
-rw-r--r--dom/quota/test/xpcshell/test_bad_origin_directory.js36
-rw-r--r--dom/quota/test/xpcshell/test_basics.js143
-rw-r--r--dom/quota/test/xpcshell/test_clearStoragesForOriginAttributesPattern.js58
-rw-r--r--dom/quota/test/xpcshell/test_clearStoragesForPrincipal.js56
-rw-r--r--dom/quota/test/xpcshell/test_clearStoragesForPrivateBrowsing.js41
-rw-r--r--dom/quota/test/xpcshell/test_createLocalStorage.js155
-rw-r--r--dom/quota/test/xpcshell/test_estimateOrigin.js80
-rw-r--r--dom/quota/test/xpcshell/test_getUsage.js129
-rw-r--r--dom/quota/test/xpcshell/test_groupMismatch.js74
-rw-r--r--dom/quota/test/xpcshell/test_initTemporaryStorage.js49
-rw-r--r--dom/quota/test/xpcshell/test_listOrigins.js80
-rw-r--r--dom/quota/test/xpcshell/test_originEndsWithDot.js70
-rw-r--r--dom/quota/test/xpcshell/test_originMismatch.js75
-rw-r--r--dom/quota/test/xpcshell/test_originWithCaret.js17
-rw-r--r--dom/quota/test/xpcshell/test_orpahnedQuotaObject.js44
-rw-r--r--dom/quota/test/xpcshell/test_persist.js121
-rw-r--r--dom/quota/test/xpcshell/test_persist_eviction.js82
-rw-r--r--dom/quota/test/xpcshell/test_persist_globalLimit.js82
-rw-r--r--dom/quota/test/xpcshell/test_persist_groupLimit.js104
-rw-r--r--dom/quota/test/xpcshell/test_removeLocalStorage.js89
-rw-r--r--dom/quota/test/xpcshell/test_simpledb.js6
-rw-r--r--dom/quota/test/xpcshell/test_specialOrigins.js55
-rw-r--r--dom/quota/test/xpcshell/test_storagePressure.js135
-rw-r--r--dom/quota/test/xpcshell/test_tempMetadataCleanup.js45
-rw-r--r--dom/quota/test/xpcshell/test_unaccessedOrigins.js168
-rw-r--r--dom/quota/test/xpcshell/test_unknownFiles.js106
-rw-r--r--dom/quota/test/xpcshell/test_unsetLastAccessTime.js68
-rw-r--r--dom/quota/test/xpcshell/test_validOrigins.js99
-rw-r--r--dom/quota/test/xpcshell/unknownFiles_profile.zipbin0 -> 13582 bytes
-rw-r--r--dom/quota/test/xpcshell/unsetLastAccessTime_profile.zipbin0 -> 1574 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/cacheVersion1_profile.json64
-rw-r--r--dom/quota/test/xpcshell/upgrades/cacheVersion1_profile.zipbin0 -> 2675 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/head.js14
-rw-r--r--dom/quota/test/xpcshell/upgrades/indexedDBAndPersistentStorageDirectory_profile.json63
-rw-r--r--dom/quota/test/xpcshell/upgrades/indexedDBAndPersistentStorageDirectory_profile.zipbin0 -> 1336 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/indexedDBDirectory_flatOriginDirectories_profile.json55
-rw-r--r--dom/quota/test/xpcshell/upgrades/indexedDBDirectory_flatOriginDirectories_profile.zipbin0 -> 812 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/indexedDBDirectory_profile.json65
-rw-r--r--dom/quota/test/xpcshell/upgrades/indexedDBDirectory_profile.zipbin0 -> 1508 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/localStorageArchive1upgrade_profile.zipbin0 -> 7675 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/localStorageArchive4upgrade_profile.zipbin0 -> 7853 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/localStorageArchiveDowngrade_profile.zipbin0 -> 7799 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/persistentAndDefaultStorageDirectory_profile.json63
-rw-r--r--dom/quota/test/xpcshell/upgrades/persistentAndDefaultStorageDirectory_profile.zipbin0 -> 1413 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_flatOriginDirectories_profile.json64
-rw-r--r--dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_flatOriginDirectories_profile.zipbin0 -> 1028 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_originDirectories_profile.json92
-rw-r--r--dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_originDirectories_profile.zipbin0 -> 2720 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_profile.json382
-rw-r--r--dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_profile.zipbin0 -> 12717 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_localStorageArchive1upgrade.js65
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_localStorageArchive4upgrade.js107
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_localStorageArchiveDowngrade.js66
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeCacheFrom1.js79
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeFromFlatOriginDirectories.js187
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeFromIndexedDBDirectory.js121
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeFromIndexedDBDirectory_removeOldDirectory.js86
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory.js378
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory_removeOldDirectory.js102
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory_upgradeOriginDirectories.js162
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom0_0.js158
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_idb.js43
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_removeAppsData.js100
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_removeMorgueDirectory.js60
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_stripObsoleteOriginAttributes.js179
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_0.js97
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_1.js85
-rw-r--r--dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_2.js64
-rw-r--r--dom/quota/test/xpcshell/upgrades/version0_0_profile.json88
-rw-r--r--dom/quota/test/xpcshell/upgrades/version0_0_profile.zipbin0 -> 2684 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/version1_0_appsData_profile.json72
-rw-r--r--dom/quota/test/xpcshell/upgrades/version1_0_appsData_profile.zipbin0 -> 3145 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/version1_0_idb_profile.json73
-rw-r--r--dom/quota/test/xpcshell/upgrades/version1_0_idb_profile.zipbin0 -> 3944 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/version1_0_morgueDirectory_profile.json57
-rw-r--r--dom/quota/test/xpcshell/upgrades/version1_0_morgueDirectory_profile.zipbin0 -> 1438 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/version1_0_obsoleteOriginAttributes_profile.json112
-rw-r--r--dom/quota/test/xpcshell/upgrades/version1_0_obsoleteOriginAttributes_profile.zipbin0 -> 4843 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/version2_0_profile.json105
-rw-r--r--dom/quota/test/xpcshell/upgrades/version2_0_profile.zipbin0 -> 5119 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/version2_1_profile.json69
-rw-r--r--dom/quota/test/xpcshell/upgrades/version2_1_profile.zipbin0 -> 2400 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/version2_2_profile.json18
-rw-r--r--dom/quota/test/xpcshell/upgrades/version2_2_profile.zipbin0 -> 237 bytes
-rw-r--r--dom/quota/test/xpcshell/upgrades/xpcshell.ini61
-rw-r--r--dom/quota/test/xpcshell/xpcshell.ini65
206 files changed, 15842 insertions, 0 deletions
diff --git a/dom/quota/test/browser/browser.ini b/dom/quota/test/browser/browser.ini
new file mode 100644
index 0000000000..d0c79f90a9
--- /dev/null
+++ b/dom/quota/test/browser/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+skip-if = (buildapp != "browser")
+support-files =
+ head.js
+ helpers.js
+ empty.html
+ permissionsPrompt.html
+
+[browser_permissionsCrossOrigin.js]
+[browser_permissionsPromptAllow.js]
+[browser_permissionsPromptDeny.js]
+[browser_permissionsPromptUnknown.js]
+[browser_simpledb.js]
diff --git a/dom/quota/test/browser/browser_permissionsCrossOrigin.js b/dom/quota/test/browser/browser_permissionsCrossOrigin.js
new file mode 100644
index 0000000000..759eff73a3
--- /dev/null
+++ b/dom/quota/test/browser/browser_permissionsCrossOrigin.js
@@ -0,0 +1,56 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const emptyURL =
+ "https://example.com/browser/dom/quota/test/browser/empty.html";
+
+addTest(async function testNoPermissionPrompt() {
+ registerPopupEventHandler("popupshowing", function () {
+ ok(false, "Shouldn't show a popup this time");
+ });
+ registerPopupEventHandler("popupshown", function () {
+ ok(false, "Shouldn't show a popup this time");
+ });
+ registerPopupEventHandler("popuphidden", function () {
+ ok(false, "Shouldn't show a popup this time");
+ });
+
+ info("Creating tab");
+
+ await BrowserTestUtils.withNewTab(emptyURL, async function (browser) {
+ await new Promise(r => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [
+ ["permissions.delegation.enabled", true],
+ ["dom.security.featurePolicy.header.enabled", true],
+ ["dom.security.featurePolicy.webidl.enabled", true],
+ ],
+ },
+ r
+ );
+ });
+
+ await SpecialPowers.spawn(browser, [], async function (host0) {
+ let frame = content.document.createElement("iframe");
+ // Cross origin src
+ frame.src = "https://example.org/browser/dom/quota/test/empty.html";
+ content.document.body.appendChild(frame);
+ await ContentTaskUtils.waitForEvent(frame, "load");
+
+ await content.SpecialPowers.spawn(frame, [], async function () {
+ // Request a permission.
+ const persistAllowed = await this.content.navigator.storage.persist();
+ Assert.ok(
+ !persistAllowed,
+ "navigator.storage.persist() has been denied"
+ );
+ });
+ content.document.body.removeChild(frame);
+ });
+ });
+
+ unregisterAllPopupEventHandlers();
+});
diff --git a/dom/quota/test/browser/browser_permissionsPromptAllow.js b/dom/quota/test/browser/browser_permissionsPromptAllow.js
new file mode 100644
index 0000000000..12cd0b7d79
--- /dev/null
+++ b/dom/quota/test/browser/browser_permissionsPromptAllow.js
@@ -0,0 +1,66 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const testPageURL =
+ "https://example.com/browser/dom/quota/test/browser/permissionsPrompt.html";
+
+addTest(async function testPermissionAllow() {
+ removePermission(testPageURL, "persistent-storage");
+
+ registerPopupEventHandler("popupshowing", function () {
+ ok(true, "prompt showing");
+ });
+ registerPopupEventHandler("popupshown", function () {
+ ok(true, "prompt shown");
+ triggerMainCommand(this);
+ });
+ registerPopupEventHandler("popuphidden", function () {
+ ok(true, "prompt hidden");
+ });
+
+ info("Creating tab");
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ info("Loading test page: " + testPageURL);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, testPageURL);
+ await waitForMessage(true, gBrowser);
+
+ is(
+ getPermission(testPageURL, "persistent-storage"),
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ "Correct permission set"
+ );
+ gBrowser.removeCurrentTab();
+ unregisterAllPopupEventHandlers();
+ // Keep persistent-storage permission for the next test.
+});
+
+addTest(async function testNoPermissionPrompt() {
+ registerPopupEventHandler("popupshowing", function () {
+ ok(false, "Shouldn't show a popup this time");
+ });
+ registerPopupEventHandler("popupshown", function () {
+ ok(false, "Shouldn't show a popup this time");
+ });
+ registerPopupEventHandler("popuphidden", function () {
+ ok(false, "Shouldn't show a popup this time");
+ });
+
+ info("Creating tab");
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ info("Loading test page: " + testPageURL);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, testPageURL);
+ await waitForMessage(true, gBrowser);
+
+ is(
+ getPermission(testPageURL, "persistent-storage"),
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ "Correct permission set"
+ );
+ gBrowser.removeCurrentTab();
+ unregisterAllPopupEventHandlers();
+ removePermission(testPageURL, "persistent-storage");
+});
diff --git a/dom/quota/test/browser/browser_permissionsPromptDeny.js b/dom/quota/test/browser/browser_permissionsPromptDeny.js
new file mode 100644
index 0000000000..6213e4519f
--- /dev/null
+++ b/dom/quota/test/browser/browser_permissionsPromptDeny.js
@@ -0,0 +1,150 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const testPageURL =
+ "https://example.com/browser/dom/quota/test/browser/permissionsPrompt.html";
+
+addTest(async function testPermissionTemporaryDenied() {
+ registerPopupEventHandler("popupshowing", function () {
+ ok(true, "prompt showing");
+ });
+ registerPopupEventHandler("popupshown", function () {
+ ok(true, "prompt shown");
+ triggerSecondaryCommand(this);
+ });
+ registerPopupEventHandler("popuphidden", function () {
+ ok(true, "prompt hidden");
+ });
+
+ info("Creating tab");
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ info("Loading test page: " + testPageURL);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, testPageURL);
+ await waitForMessage(false, gBrowser);
+
+ is(
+ getPermission(testPageURL, "persistent-storage"),
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ "Correct permission set"
+ );
+
+ let tempBlock = SitePermissions.getAllForBrowser(
+ gBrowser.selectedBrowser
+ ).find(
+ p =>
+ p.id == "persistent-storage" &&
+ p.state == SitePermissions.BLOCK &&
+ p.scope == SitePermissions.SCOPE_TEMPORARY
+ );
+ ok(tempBlock, "Should have a temporary block permission on active browser");
+
+ unregisterAllPopupEventHandlers();
+ gBrowser.removeCurrentTab();
+ removePermission(testPageURL, "persistent-storage");
+});
+
+addTest(async function testPermissionDenied() {
+ removePermission(testPageURL, "persistent-storage");
+
+ registerPopupEventHandler("popupshowing", function () {
+ ok(true, "prompt showing");
+ });
+ registerPopupEventHandler("popupshown", function () {
+ ok(true, "prompt shown");
+ triggerSecondaryCommand(this, /* remember = */ true);
+ });
+ registerPopupEventHandler("popuphidden", function () {
+ ok(true, "prompt hidden");
+ });
+
+ info("Creating tab");
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ info("Loading test page: " + testPageURL);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, testPageURL);
+ await waitForMessage(false, gBrowser);
+
+ is(
+ getPermission(testPageURL, "persistent-storage"),
+ Ci.nsIPermissionManager.DENY_ACTION,
+ "Correct permission set"
+ );
+ unregisterAllPopupEventHandlers();
+ gBrowser.removeCurrentTab();
+ // Keep persistent-storage permission for the next test.
+});
+
+addTest(async function testNoPermissionPrompt() {
+ registerPopupEventHandler("popupshowing", function () {
+ ok(false, "Shouldn't show a popup this time");
+ });
+ registerPopupEventHandler("popupshown", function () {
+ ok(false, "Shouldn't show a popup this time");
+ });
+ registerPopupEventHandler("popuphidden", function () {
+ ok(false, "Shouldn't show a popup this time");
+ });
+
+ info("Creating tab");
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ info("Loading test page: " + testPageURL);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, testPageURL);
+ await waitForMessage(false, gBrowser);
+
+ is(
+ getPermission(testPageURL, "persistent-storage"),
+ Ci.nsIPermissionManager.DENY_ACTION,
+ "Correct permission set"
+ );
+ unregisterAllPopupEventHandlers();
+ gBrowser.removeCurrentTab();
+ removePermission(testPageURL, "persistent-storage");
+});
+
+addTest(async function testPermissionDeniedDismiss() {
+ registerPopupEventHandler("popupshowing", function () {
+ ok(true, "prompt showing");
+ });
+ registerPopupEventHandler("popupshown", function () {
+ ok(true, "prompt shown");
+ // Dismiss permission prompt.
+ dismissNotification(this);
+ });
+ registerPopupEventHandler("popuphidden", function () {
+ ok(true, "prompt hidden");
+ });
+
+ info("Creating tab");
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ info("Loading test page: " + testPageURL);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, testPageURL);
+ await waitForMessage(false, gBrowser);
+
+ // Pressing ESC results in a temporary block permission on the browser object.
+ // So the global permission for the URL should still be unknown, but the browser
+ // should have a block permission with a temporary scope.
+ is(
+ getPermission(testPageURL, "persistent-storage"),
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ "Correct permission set"
+ );
+
+ let tempBlock = SitePermissions.getAllForBrowser(
+ gBrowser.selectedBrowser
+ ).find(
+ p =>
+ p.id == "persistent-storage" &&
+ p.state == SitePermissions.BLOCK &&
+ p.scope == SitePermissions.SCOPE_TEMPORARY
+ );
+ ok(tempBlock, "Should have a temporary block permission on active browser");
+
+ unregisterAllPopupEventHandlers();
+ gBrowser.removeCurrentTab();
+ removePermission(testPageURL, "persistent-storage");
+});
diff --git a/dom/quota/test/browser/browser_permissionsPromptUnknown.js b/dom/quota/test/browser/browser_permissionsPromptUnknown.js
new file mode 100644
index 0000000000..7e3f09ccaa
--- /dev/null
+++ b/dom/quota/test/browser/browser_permissionsPromptUnknown.js
@@ -0,0 +1,53 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const testPageURL =
+ "https://example.com/browser/dom/quota/test/browser/permissionsPrompt.html";
+
+addTest(async function testPermissionUnknownInPrivateWindow() {
+ removePermission(testPageURL, "persistent-storage");
+ info("Creating private window");
+ let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
+
+ registerPopupEventHandler(
+ "popupshowing",
+ function () {
+ ok(false, "Shouldn't show a popup this time");
+ },
+ win
+ );
+ registerPopupEventHandler(
+ "popupshown",
+ function () {
+ ok(false, "Shouldn't show a popup this time");
+ },
+ win
+ );
+ registerPopupEventHandler(
+ "popuphidden",
+ function () {
+ ok(false, "Shouldn't show a popup this time");
+ },
+ win
+ );
+
+ info("Creating private tab");
+ win.gBrowser.selectedTab = BrowserTestUtils.addTab(win.gBrowser);
+
+ info("Loading test page: " + testPageURL);
+ BrowserTestUtils.loadURIString(win.gBrowser.selectedBrowser, testPageURL);
+ await waitForMessage(false, win.gBrowser);
+
+ is(
+ getPermission(testPageURL, "persistent-storage"),
+ Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ "Correct permission set"
+ );
+ unregisterAllPopupEventHandlers(win);
+ win.gBrowser.removeCurrentTab();
+ await BrowserTestUtils.closeWindow(win);
+ win = null;
+ removePermission(testPageURL, "persistent-storage");
+});
diff --git a/dom/quota/test/browser/browser_simpledb.js b/dom/quota/test/browser/browser_simpledb.js
new file mode 100644
index 0000000000..b3be78fca6
--- /dev/null
+++ b/dom/quota/test/browser/browser_simpledb.js
@@ -0,0 +1,51 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// getRandomBuffer, compareBuffers
+loadScript("dom/quota/test/common/file.js");
+
+addTest(async function testSimpleDB() {
+ const name = "data";
+ const bufferSize = 100;
+
+ let database = getSimpleDatabase();
+
+ let request = database.open("data");
+ await requestFinished(request);
+
+ let buffer1 = getRandomBuffer(bufferSize);
+
+ request = database.write(buffer1);
+ await requestFinished(request);
+
+ request = database.seek(0);
+ await requestFinished(request);
+
+ request = database.read(bufferSize);
+ let result = await requestFinished(request);
+
+ let buffer2 = result.getAsArrayBuffer();
+
+ ok(compareBuffers(buffer1, buffer2), "Buffers equal.");
+
+ let database2 = getSimpleDatabase();
+
+ try {
+ request = database2.open(name);
+ await requestFinished(request);
+ ok(false, "Should have thrown!");
+ } catch (ex) {
+ ok(request.resultCode == NS_ERROR_STORAGE_BUSY, "Good result code.");
+ }
+
+ request = database.close();
+ await requestFinished(request);
+
+ request = database2.open(name);
+ await requestFinished(request);
+
+ request = database2.close();
+ await requestFinished(request);
+});
diff --git a/dom/quota/test/browser/empty.html b/dom/quota/test/browser/empty.html
new file mode 100644
index 0000000000..1ad28bb1f7
--- /dev/null
+++ b/dom/quota/test/browser/empty.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Empty file</title>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/quota/test/browser/head.js b/dom/quota/test/browser/head.js
new file mode 100644
index 0000000000..38b2e82cf0
--- /dev/null
+++ b/dom/quota/test/browser/head.js
@@ -0,0 +1,149 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// The path to the top level directory.
+const depth = "../../../../";
+
+var gActiveListeners = {};
+
+loadScript("dom/quota/test/common/browser.js");
+
+function loadScript(path) {
+ const url = new URL(depth + path, gTestPath);
+ Services.scriptloader.loadSubScript(url.href, this);
+}
+
+// These event (un)registration handlers only work for one window, DONOT use
+// them with multiple windows.
+
+function registerPopupEventHandler(eventName, callback, win) {
+ if (!win) {
+ win = window;
+ }
+ gActiveListeners[eventName] = function (event) {
+ if (event.target != win.PopupNotifications.panel) {
+ return;
+ }
+ win.PopupNotifications.panel.removeEventListener(
+ eventName,
+ gActiveListeners[eventName]
+ );
+ delete gActiveListeners[eventName];
+
+ callback.call(win.PopupNotifications.panel);
+ };
+ win.PopupNotifications.panel.addEventListener(
+ eventName,
+ gActiveListeners[eventName]
+ );
+}
+
+function unregisterAllPopupEventHandlers(win) {
+ if (!win) {
+ win = window;
+ }
+ for (let eventName in gActiveListeners) {
+ win.PopupNotifications.panel.removeEventListener(
+ eventName,
+ gActiveListeners[eventName]
+ );
+ }
+ gActiveListeners = {};
+}
+
+function triggerMainCommand(popup, win) {
+ if (!win) {
+ win = window;
+ }
+ info("triggering main command");
+ let notifications = popup.childNodes;
+ ok(notifications.length, "at least one notification displayed");
+ let notification = notifications[0];
+ info("triggering command: " + notification.getAttribute("buttonlabel"));
+
+ EventUtils.synthesizeMouseAtCenter(notification.button, {}, win);
+}
+
+async function triggerSecondaryCommand(popup, remember = false, win = window) {
+ info("triggering secondary command");
+ let notifications = popup.childNodes;
+ ok(notifications.length, "at least one notification displayed");
+ let notification = notifications[0];
+
+ if (remember) {
+ notification.checkbox.checked = true;
+ }
+
+ await EventUtils.synthesizeMouseAtCenter(
+ notification.secondaryButton,
+ {},
+ win
+ );
+}
+
+function dismissNotification(popup, win = window) {
+ info("dismissing notification");
+ executeSoon(function () {
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+ });
+}
+
+function waitForMessage(aMessage, browser) {
+ // We cannot capture aMessage inside the checkFn, so we override the
+ // checkFn.toSource to tunnel aMessage instead.
+ let checkFn = function () {};
+ checkFn.toSource = function () {
+ return `function checkFn(event) {
+ let message = ${aMessage.toSource()};
+ if (event.data == message) {
+ return true;
+ }
+ throw new Error(
+ \`Unexpected result: \$\{event.data\}, expected \$\{message\}\`
+ );
+ }`;
+ };
+
+ return BrowserTestUtils.waitForContentEvent(
+ browser.selectedBrowser,
+ "message",
+ /* capture */ true,
+ checkFn,
+ /* wantsUntrusted */ true
+ ).then(() => {
+ // An assertion in checkFn wouldn't be recorded as part of the test, so we
+ // use this assertion to confirm that we've successfully received the
+ // message (we'll only reach this point if that's the case).
+ ok(true, "Received message: " + aMessage);
+ });
+}
+
+function removePermission(url, permission) {
+ let uri = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .newURI(url);
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ let principal = ssm.createContentPrincipal(uri, {});
+
+ Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager)
+ .removeFromPrincipal(principal, permission);
+}
+
+function getPermission(url, permission) {
+ let uri = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService)
+ .newURI(url);
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(
+ Ci.nsIScriptSecurityManager
+ );
+ let principal = ssm.createContentPrincipal(uri, {});
+
+ return Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager)
+ .testPermissionFromPrincipal(principal, permission);
+}
diff --git a/dom/quota/test/browser/helpers.js b/dom/quota/test/browser/helpers.js
new file mode 100644
index 0000000000..f3bbb36b71
--- /dev/null
+++ b/dom/quota/test/browser/helpers.js
@@ -0,0 +1,46 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// The path to the top level directory.
+const depth = "../../../../";
+
+var testGenerator;
+var testResult;
+
+loadScript("dom/quota/test/common/nestedtest.js");
+
+function loadScript(path) {
+ const url = new URL(depth + path, window.location.href);
+ SpecialPowers.Services.scriptloader.loadSubScript(url.href, this);
+}
+
+function runTest() {
+ clearAllDatabases(() => {
+ testGenerator = testSteps();
+ testGenerator.next();
+ });
+}
+
+function finishTestNow() {
+ if (testGenerator) {
+ testGenerator.return();
+ testGenerator = undefined;
+ }
+}
+
+function finishTest() {
+ clearAllDatabases(() => {
+ setTimeout(finishTestNow, 0);
+ setTimeout(() => {
+ window.parent.postMessage(testResult, "*");
+ }, 0);
+ });
+}
+
+function continueToNextStep() {
+ setTimeout(() => {
+ testGenerator.next();
+ }, 0);
+}
diff --git a/dom/quota/test/browser/permissionsPrompt.html b/dom/quota/test/browser/permissionsPrompt.html
new file mode 100644
index 0000000000..5f11bf6c95
--- /dev/null
+++ b/dom/quota/test/browser/permissionsPrompt.html
@@ -0,0 +1,34 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+ <head>
+ <meta charset=UTF-8>
+ <title>Persistent-Storage Permission Prompt Test</title>
+
+ <script type="text/javascript" src="helpers.js"></script>
+
+ <script type="text/javascript">
+ function* testSteps()
+ {
+ SpecialPowers.pushPrefEnv({
+ "set": [["dom.storageManager.prompt.testing", false],
+ ["dom.storageManager.prompt.testing.allow", false]]
+ }, continueToNextStep);
+ yield undefined;
+
+ navigator.storage.persist().then(result => {
+ testGenerator.next(result);
+ });
+ testResult = yield undefined;
+
+ finishTest();
+ }
+ </script>
+
+ </head>
+
+ <body onload="runTest();" onunload="finishTestNow();"></body>
+
+</html>
diff --git a/dom/quota/test/common/browser.js b/dom/quota/test/common/browser.js
new file mode 100644
index 0000000000..b9903184df
--- /dev/null
+++ b/dom/quota/test/common/browser.js
@@ -0,0 +1,34 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+loadScript("dom/quota/test/common/system.js");
+
+function addTest(testFunction) {
+ const taskFunction = async function () {
+ await enableStorageTesting();
+
+ await testFunction();
+ };
+
+ Object.defineProperty(taskFunction, "name", {
+ value: testFunction.name,
+ writable: false,
+ });
+
+ add_task(taskFunction);
+}
+
+async function enableStorageTesting() {
+ const prefsToSet = [
+ ["dom.quotaManager.testing", true],
+ ["dom.storageManager.enabled", true],
+ ["dom.simpleDB.enabled", true],
+ ];
+ if (Services.appinfo.OS === "WINNT") {
+ prefsToSet.push(["dom.quotaManager.useDOSDevicePathSyntax", true]);
+ }
+
+ await SpecialPowers.pushPrefEnv({ set: prefsToSet });
+}
diff --git a/dom/quota/test/common/content.js b/dom/quota/test/common/content.js
new file mode 100644
index 0000000000..51c9d3d68d
--- /dev/null
+++ b/dom/quota/test/common/content.js
@@ -0,0 +1,62 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const NS_ERROR_STORAGE_BUSY = SpecialPowers.Cr.NS_ERROR_STORAGE_BUSY;
+
+loadScript("dom/quota/test/common/global.js");
+
+function clearAllDatabases(callback) {
+ let qms = SpecialPowers.Services.qms;
+ let principal = SpecialPowers.wrap(document).nodePrincipal;
+ let request = qms.clearStoragesForPrincipal(principal);
+ let cb = SpecialPowers.wrapCallback(callback);
+ request.callback = cb;
+ return request;
+}
+
+// SimpleDB connections and SpecialPowers wrapping:
+//
+// SpecialPowers provides a SpecialPowersHandler Proxy mechanism that lets our
+// content-privileged code borrow its chrome-privileged principal to access
+// things we shouldn't be able to access. The proxies wrap their returned
+// values, so once we have something wrapped we can rely on returned objects
+// being wrapped as well. The proxy will also automatically unwrap wrapped
+// arguments we pass in. However, we need to invoke wrapCallback on callback
+// functions so that the arguments they receive will be wrapped because the
+// proxy does not automatically wrap content-privileged functions.
+//
+// Our use of (wrapped) SpecialPowers.Cc results in getSimpleDatabase()
+// producing a wrapped nsISDBConnection instance. The nsISDBResult instances
+// exposed on the (wrapped) nsISDBRequest are also wrapped.
+// In particular, the wrapper takes responsibility for automatically cloning
+// the ArrayBuffer returned by nsISDBResult.getAsArrayBuffer into the content
+// compartment (rather than wrapping it) so that constructing a Uint8Array
+// from it will succeed.
+
+function getSimpleDatabase() {
+ let connection = SpecialPowers.Cc[
+ "@mozilla.org/dom/sdb-connection;1"
+ ].createInstance(SpecialPowers.Ci.nsISDBConnection);
+
+ let principal = SpecialPowers.wrap(document).nodePrincipal;
+
+ connection.init(principal);
+
+ return connection;
+}
+
+async function requestFinished(request) {
+ await new Promise(function (resolve) {
+ request.callback = SpecialPowers.wrapCallback(function () {
+ resolve();
+ });
+ });
+
+ if (request.resultCode != SpecialPowers.Cr.NS_OK) {
+ throw new RequestError(request.resultCode, request.resultName);
+ }
+
+ return request.result;
+}
diff --git a/dom/quota/test/common/file.js b/dom/quota/test/common/file.js
new file mode 100644
index 0000000000..55e2e189fb
--- /dev/null
+++ b/dom/quota/test/common/file.js
@@ -0,0 +1,45 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function getBuffer(size) {
+ let buffer = new ArrayBuffer(size);
+ is(buffer.byteLength, size, "Correct byte length");
+ return buffer;
+}
+
+// May be called for any size, but you should call getBuffer() if you know
+// that size is big and that randomness is not necessary because it is
+// noticeably faster.
+function getRandomBuffer(size) {
+ let buffer = getBuffer(size);
+ let view = new Uint8Array(buffer);
+ for (let i = 0; i < size; i++) {
+ view[i] = parseInt(Math.random() * 255);
+ }
+ return buffer;
+}
+
+function compareBuffers(buffer1, buffer2) {
+ if (buffer1.byteLength != buffer2.byteLength) {
+ return false;
+ }
+
+ let view1 = buffer1 instanceof Uint8Array ? buffer1 : new Uint8Array(buffer1);
+ let view2 = buffer2 instanceof Uint8Array ? buffer2 : new Uint8Array(buffer2);
+ for (let i = 0; i < buffer1.byteLength; i++) {
+ if (view1[i] != view2[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function getBlob(type, object) {
+ return new Blob([object], { type });
+}
+
+function getNullBlob(size) {
+ return getBlob("binary/null", getBuffer(size));
+}
diff --git a/dom/quota/test/common/global.js b/dom/quota/test/common/global.js
new file mode 100644
index 0000000000..cc7d9d97a2
--- /dev/null
+++ b/dom/quota/test/common/global.js
@@ -0,0 +1,47 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const INT64_MIN = -0x8000000000000000n;
+
+class RequestError extends Error {
+ constructor(resultCode, resultName) {
+ super(`Request failed (code: ${resultCode}, name: ${resultName})`);
+ this.name = "RequestError";
+ this.resultCode = resultCode;
+ this.resultName = resultName;
+ }
+}
+
+function openDBRequestUpgradeNeeded(request) {
+ return new Promise(function (resolve, reject) {
+ request.onerror = function (event) {
+ ok(false, "indexedDB error, '" + event.target.error.name + "'");
+ reject(event);
+ };
+ request.onupgradeneeded = function (event) {
+ resolve(event);
+ };
+ request.onsuccess = function (event) {
+ ok(false, "Got success, but did not expect it!");
+ reject(event);
+ };
+ });
+}
+
+function openDBRequestSucceeded(request) {
+ return new Promise(function (resolve, reject) {
+ request.onerror = function (event) {
+ ok(false, "indexedDB error, '" + event.target.error.name + "'");
+ reject(event);
+ };
+ request.onupgradeneeded = function (event) {
+ ok(false, "Got upgrade, but did not expect it!");
+ reject(event);
+ };
+ request.onsuccess = function (event) {
+ resolve(event);
+ };
+ });
+}
diff --git a/dom/quota/test/common/mochitest.js b/dom/quota/test/common/mochitest.js
new file mode 100644
index 0000000000..1b867f6e92
--- /dev/null
+++ b/dom/quota/test/common/mochitest.js
@@ -0,0 +1,19 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+loadScript("dom/quota/test/common/content.js");
+
+async function enableStorageTesting() {
+ let prefsToSet = [
+ ["dom.quotaManager.testing", true],
+ ["dom.storageManager.enabled", true],
+ ["dom.simpleDB.enabled", true],
+ ];
+ if (SpecialPowers.Services.appinfo.OS === "WINNT") {
+ prefsToSet.push(["dom.quotaManager.useDOSDevicePathSyntax", true]);
+ }
+
+ await SpecialPowers.pushPrefEnv({ set: prefsToSet });
+}
diff --git a/dom/quota/test/common/nestedtest.js b/dom/quota/test/common/nestedtest.js
new file mode 100644
index 0000000000..5c2011bfe9
--- /dev/null
+++ b/dom/quota/test/common/nestedtest.js
@@ -0,0 +1,6 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+loadScript("dom/quota/test/common/content.js");
diff --git a/dom/quota/test/common/system.js b/dom/quota/test/common/system.js
new file mode 100644
index 0000000000..b01bb8d1fa
--- /dev/null
+++ b/dom/quota/test/common/system.js
@@ -0,0 +1,74 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const PR_USEC_PER_SEC = 1000000;
+
+const NS_ERROR_STORAGE_BUSY = Cr.NS_ERROR_STORAGE_BUSY;
+
+loadScript("dom/quota/test/common/global.js");
+
+function getProfileDir() {
+ return Services.dirsvc.get("ProfD", Ci.nsIFile);
+}
+
+// Given a "/"-delimited path relative to a base file (or the profile
+// directory if a base file is not provided) return an nsIFile representing the
+// path. This does not test for the existence of the file or parent
+// directories. It is safe even on Windows where the directory separator is
+// not "/", but make sure you're not passing in a "\"-delimited path.
+function getRelativeFile(relativePath, baseFile) {
+ if (!baseFile) {
+ baseFile = getProfileDir();
+ }
+
+ let file = baseFile.clone();
+
+ if (Services.appinfo.OS === "WINNT") {
+ let winFile = file.QueryInterface(Ci.nsILocalFileWin);
+ winFile.useDOSDevicePathSyntax = true;
+ }
+
+ relativePath.split("/").forEach(function (component) {
+ if (component == "..") {
+ file = file.parent;
+ } else {
+ file.append(component);
+ }
+ });
+
+ return file;
+}
+
+function getCurrentPrincipal() {
+ return Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
+}
+
+function getSimpleDatabase(principal, persistence) {
+ let connection = Cc["@mozilla.org/dom/sdb-connection;1"].createInstance(
+ Ci.nsISDBConnection
+ );
+
+ if (!principal) {
+ principal = getCurrentPrincipal();
+ }
+
+ connection.init(principal, persistence);
+
+ return connection;
+}
+
+async function requestFinished(request) {
+ await new Promise(function (resolve) {
+ request.callback = function () {
+ resolve();
+ };
+ });
+
+ if (request.resultCode !== Cr.NS_OK) {
+ throw new RequestError(request.resultCode, request.resultName);
+ }
+
+ return request.result;
+}
diff --git a/dom/quota/test/common/test_simpledb.js b/dom/quota/test/common/test_simpledb.js
new file mode 100644
index 0000000000..dee7019097
--- /dev/null
+++ b/dom/quota/test/common/test_simpledb.js
@@ -0,0 +1,50 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+loadScript("dom/quota/test/common/file.js");
+
+async function testSteps() {
+ const name = "data";
+ const bufferSize = 100;
+
+ let database = getSimpleDatabase();
+
+ let request = database.open(name);
+ await requestFinished(request);
+
+ let buffer1 = getRandomBuffer(bufferSize);
+
+ request = database.write(buffer1);
+ await requestFinished(request);
+
+ request = database.seek(0);
+ await requestFinished(request);
+
+ request = database.read(bufferSize);
+ let result = await requestFinished(request);
+
+ let buffer2 = result.getAsArrayBuffer();
+
+ ok(compareBuffers(buffer1, buffer2), "Buffers equal.");
+
+ let database2 = getSimpleDatabase();
+
+ try {
+ request = database2.open(name);
+ await requestFinished(request);
+ ok(false, "Should have thrown!");
+ } catch (ex) {
+ ok(request.resultCode == NS_ERROR_STORAGE_BUSY, "Good result code.");
+ }
+
+ request = database.close();
+ await requestFinished(request);
+
+ request = database2.open(name);
+ await requestFinished(request);
+
+ request = database2.close();
+ await requestFinished(request);
+}
diff --git a/dom/quota/test/common/test_storage_manager_persist_allow.js b/dom/quota/test/common/test_storage_manager_persist_allow.js
new file mode 100644
index 0000000000..0a6e59843d
--- /dev/null
+++ b/dom/quota/test/common/test_storage_manager_persist_allow.js
@@ -0,0 +1,30 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function* testSteps() {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [["dom.storageManager.prompt.testing.allow", true]],
+ },
+ continueToNextStep
+ );
+ yield undefined;
+
+ navigator.storage.persist().then(grabArgAndContinueHandler);
+ let persistResult = yield undefined;
+
+ is(persistResult, true, "Persist succeeded");
+
+ navigator.storage.persisted().then(grabArgAndContinueHandler);
+ let persistedResult = yield undefined;
+
+ is(
+ persistResult,
+ persistedResult,
+ "Persist/persisted results are consistent"
+ );
+
+ finishTest();
+}
diff --git a/dom/quota/test/common/test_storage_manager_persist_deny.js b/dom/quota/test/common/test_storage_manager_persist_deny.js
new file mode 100644
index 0000000000..855d739ca3
--- /dev/null
+++ b/dom/quota/test/common/test_storage_manager_persist_deny.js
@@ -0,0 +1,34 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function* testSteps() {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [["dom.storageManager.prompt.testing.allow", false]],
+ },
+ continueToNextStep
+ );
+ yield undefined;
+
+ navigator.storage.persist().then(grabArgAndContinueHandler);
+ let persistResult = yield undefined;
+
+ is(
+ persistResult,
+ false,
+ "Cancel the persist prompt and resolve a promise with false"
+ );
+
+ navigator.storage.persisted().then(grabArgAndContinueHandler);
+ let persistedResult = yield undefined;
+
+ is(
+ persistResult,
+ persistedResult,
+ "Persist/persisted results are consistent"
+ );
+
+ finishTest();
+}
diff --git a/dom/quota/test/common/test_storage_manager_persisted.js b/dom/quota/test/common/test_storage_manager_persisted.js
new file mode 100644
index 0000000000..ebda93649a
--- /dev/null
+++ b/dom/quota/test/common/test_storage_manager_persisted.js
@@ -0,0 +1,13 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function* testSteps() {
+ navigator.storage.persisted().then(grabArgAndContinueHandler);
+ let persistedResult = yield undefined;
+
+ is(persistedResult, false, "Persisted returns false");
+
+ finishTest();
+}
diff --git a/dom/quota/test/common/xpcshell.js b/dom/quota/test/common/xpcshell.js
new file mode 100644
index 0000000000..ed3afaa467
--- /dev/null
+++ b/dom/quota/test/common/xpcshell.js
@@ -0,0 +1,91 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+loadScript("dom/quota/test/common/system.js");
+
+function enableStorageTesting() {
+ Services.prefs.setBoolPref("dom.quotaManager.testing", true);
+ Services.prefs.setBoolPref("dom.storageManager.enabled", true);
+ Services.prefs.setBoolPref("dom.simpleDB.enabled", true);
+ if (Services.appinfo.OS === "WINNT") {
+ Services.prefs.setBoolPref("dom.quotaManager.useDOSDevicePathSyntax", true);
+ }
+}
+
+function resetStorageTesting() {
+ Services.prefs.clearUserPref("dom.quotaManager.testing");
+ Services.prefs.clearUserPref("dom.storageManager.enabled");
+ Services.prefs.clearUserPref("dom.simpleDB.enabled");
+ if (Services.appinfo.OS === "WINNT") {
+ Services.prefs.clearUserPref("dom.quotaManager.useDOSDevicePathSyntax");
+ }
+}
+
+function clear(callback) {
+ let request = Services.qms.clear();
+ request.callback = callback;
+
+ return request;
+}
+
+function reset(callback) {
+ let request = Services.qms.reset();
+ request.callback = callback;
+
+ return request;
+}
+
+function installPackage(packageRelativePath, allowFileOverwrites) {
+ let currentDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+
+ let packageFile = getRelativeFile(packageRelativePath + ".zip", currentDir);
+
+ let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(
+ Ci.nsIZipReader
+ );
+ zipReader.open(packageFile);
+
+ let entryNames = Array.from(zipReader.findEntries(null));
+ entryNames.sort();
+
+ for (let entryName of entryNames) {
+ if (entryName.match(/^create_db\.(html|js)/)) {
+ continue;
+ }
+
+ let zipentry = zipReader.getEntry(entryName);
+
+ let file = getRelativeFile(entryName);
+
+ if (zipentry.isDirectory) {
+ if (!file.exists()) {
+ file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+ }
+ } else {
+ if (!allowFileOverwrites && file.exists()) {
+ throw new Error("File already exists!");
+ }
+
+ let istream = zipReader.getInputStream(entryName);
+
+ var ostream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, parseInt("0644", 8), 0);
+
+ let bostream = Cc[
+ "@mozilla.org/network/buffered-output-stream;1"
+ ].createInstance(Ci.nsIBufferedOutputStream);
+ bostream.init(ostream, 32768);
+
+ bostream.writeFrom(istream, istream.available());
+
+ istream.close();
+ bostream.close();
+ }
+ }
+
+ zipReader.close();
+}
diff --git a/dom/quota/test/gtest/Common.cpp b/dom/quota/test/gtest/Common.cpp
new file mode 100644
index 0000000000..efbfd94775
--- /dev/null
+++ b/dom/quota/test/gtest/Common.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Common.h"
+
+#include "mozilla/dom/QMResult.h"
+
+namespace mozilla::dom::quota {
+
+#ifdef QM_ERROR_STACKS_ENABLED
+uint64_t DOM_Quota_Test::sExpectedStackId;
+
+// static
+void DOM_Quota_Test::SetUpTestCase() {
+ sExpectedStackId = QMResult().StackId();
+}
+
+// static
+void DOM_Quota_Test::IncreaseExpectedStackId() { sExpectedStackId++; }
+
+// static
+uint64_t DOM_Quota_Test::ExpectedStackId() { return sExpectedStackId; }
+#endif
+
+} // namespace mozilla::dom::quota
diff --git a/dom/quota/test/gtest/Common.h b/dom/quota/test/gtest/Common.h
new file mode 100644
index 0000000000..eac58bd7fb
--- /dev/null
+++ b/dom/quota/test/gtest/Common.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_QUOTA_TEST_GTEST_COMMON_H_
+#define DOM_QUOTA_TEST_GTEST_COMMON_H_
+
+#include <cstdint>
+#include "gtest/gtest.h"
+#include "mozilla/dom/quota/Config.h"
+
+namespace mozilla::dom::quota {
+
+class DOM_Quota_Test : public testing::Test {
+#ifdef QM_ERROR_STACKS_ENABLED
+ public:
+ static void SetUpTestCase();
+
+ static void IncreaseExpectedStackId();
+
+ static uint64_t ExpectedStackId();
+
+ private:
+ static uint64_t sExpectedStackId;
+#endif
+};
+
+} // namespace mozilla::dom::quota
+
+#endif // DOM_QUOTA_TEST_GTEST_COMMON_H_
diff --git a/dom/quota/test/gtest/PQuotaTest.ipdl b/dom/quota/test/gtest/PQuotaTest.ipdl
new file mode 100644
index 0000000000..e4ca37325a
--- /dev/null
+++ b/dom/quota/test/gtest/PQuotaTest.ipdl
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+sync protocol PQuotaTest {
+ parent:
+ sync Try_Success_CustomErr_QmIpcFail()
+ returns (bool tryDidNotReturn);
+
+ sync Try_Success_CustomErr_IpcFail()
+ returns (bool tryDidNotReturn);
+
+ sync TryInspect_Success_CustomErr_QmIpcFail()
+ returns (bool tryDidNotReturn);
+
+ sync TryInspect_Success_CustomErr_IpcFail()
+ returns (bool tryDidNotReturn);
+};
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp b/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp
new file mode 100644
index 0000000000..2e52b4b69a
--- /dev/null
+++ b/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "QuotaManagerDependencyFixture.h"
+
+#include "mozIStorageService.h"
+#include "mozStorageCID.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/quota/QuotaManagerService.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIQuotaCallbacks.h"
+#include "nsIQuotaRequests.h"
+#include "nsIVariant.h"
+#include "nsScriptSecurityManager.h"
+
+namespace mozilla::dom::quota::test {
+
+namespace {
+
+class RequestResolver final : public nsIQuotaCallback {
+ public:
+ RequestResolver() : mDone(false) {}
+
+ bool Done() const { return mDone; }
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD OnComplete(nsIQuotaRequest* aRequest) override {
+ mDone = true;
+
+ return NS_OK;
+ }
+
+ private:
+ ~RequestResolver() = default;
+
+ bool mDone;
+};
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(RequestResolver, nsIQuotaCallback)
+
+// static
+void QuotaManagerDependencyFixture::InitializeFixture() {
+ // Some QuotaManagerService methods fail if the testing pref is not set.
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ prefs->SetBoolPref("dom.quotaManager.testing", true);
+
+ // The first initialization of storage service must be done on the main
+ // thread.
+ nsCOMPtr<mozIStorageService> storageService =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ ASSERT_TRUE(storageService);
+
+ nsIObserver* observer = QuotaManager::GetObserver();
+ ASSERT_TRUE(observer);
+
+ nsresult rv = observer->Observe(nullptr, "profile-do-change", nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ASSERT_NO_FATAL_FAILURE(StorageInitialized());
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->OwningThread());
+
+ sBackgroundTarget = quotaManager->OwningThread();
+}
+
+// static
+void QuotaManagerDependencyFixture::ShutdownFixture() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ prefs->SetBoolPref("dom.quotaManager.testing", false);
+
+ nsIObserver* observer = QuotaManager::GetObserver();
+ ASSERT_TRUE(observer);
+
+ nsresult rv = observer->Observe(nullptr, "profile-before-change-qm", nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ PerformOnBackgroundThread([]() { QuotaManager::Reset(); });
+
+ sBackgroundTarget = nullptr;
+}
+
+// static
+void QuotaManagerDependencyFixture::StorageInitialized(bool* aResult) {
+ AutoJSAPI jsapi;
+
+ bool ok = jsapi.Init(xpc::PrivilegedJunkScope());
+ ASSERT_TRUE(ok);
+
+ nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate();
+ ASSERT_TRUE(qms);
+
+ nsCOMPtr<nsIQuotaRequest> request;
+ nsresult rv = qms->StorageInitialized(getter_AddRefs(request));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ RefPtr<RequestResolver> resolver = new RequestResolver();
+
+ rv = request->SetCallback(resolver);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns,
+ [&resolver]() { return resolver->Done(); });
+
+ if (aResult) {
+ nsCOMPtr<nsIVariant> result;
+ rv = request->GetResult(getter_AddRefs(result));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ rv = result->GetAsBool(aResult);
+ ASSERT_NS_SUCCEEDED(rv);
+ }
+}
+
+// static
+void QuotaManagerDependencyFixture::ClearStoragesForOrigin(
+ const OriginMetadata& aOriginMetadata) {
+ nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate();
+ ASSERT_TRUE(qms);
+
+ nsCOMPtr<nsIScriptSecurityManager> ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+ ASSERT_TRUE(ssm);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = ssm->CreateContentPrincipalFromOrigin(
+ aOriginMetadata.mOrigin, getter_AddRefs(principal));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ nsCOMPtr<nsIQuotaRequest> request;
+ rv = qms->ClearStoragesForPrincipal(principal, VoidCString(), VoidString(),
+ /* aClearAll */ false,
+ getter_AddRefs(request));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ RefPtr<RequestResolver> resolver = new RequestResolver();
+ ASSERT_TRUE(resolver);
+
+ rv = request->SetCallback(resolver);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns,
+ [&resolver]() { return resolver->Done(); });
+}
+
+nsCOMPtr<nsISerialEventTarget> QuotaManagerDependencyFixture::sBackgroundTarget;
+
+} // namespace mozilla::dom::quota::test
diff --git a/dom/quota/test/gtest/QuotaManagerDependencyFixture.h b/dom/quota/test/gtest/QuotaManagerDependencyFixture.h
new file mode 100644
index 0000000000..4d57952242
--- /dev/null
+++ b/dom/quota/test/gtest/QuotaManagerDependencyFixture.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_QUOTA_TEST_GTEST_QUOTAMANAGERDEPENDENCYFIXTURE_H_
+#define DOM_QUOTA_TEST_GTEST_QUOTAMANAGERDEPENDENCYFIXTURE_H_
+
+#include "gtest/gtest.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/dom/quota/ForwardDecls.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+
+namespace mozilla::dom::quota::test {
+
+class QuotaManagerDependencyFixture : public testing::Test {
+ public:
+ protected:
+ static void InitializeFixture();
+
+ static void ShutdownFixture();
+
+ static void StorageInitialized(bool* aResult = nullptr);
+
+ static void ClearStoragesForOrigin(const OriginMetadata& aOriginMetadata);
+
+ /* Convenience method for tasks which must be called on PBackground thread */
+ template <class Invokable, class... Args>
+ static void PerformOnBackgroundThread(Invokable&& aInvokable,
+ Args&&... aArgs) {
+ bool done = false;
+ auto boundTask =
+ // For c++17, bind is cleaner than tuple for parameter pack forwarding
+ // NOLINTNEXTLINE(modernize-avoid-bind)
+ std::bind(std::forward<Invokable>(aInvokable),
+ std::forward<Args>(aArgs)...);
+ InvokeAsync(BackgroundTargetStrongRef(), __func__,
+ [boundTask = std::move(boundTask)] {
+ boundTask();
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& /* aValue */) {
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ }
+
+ /* Convenience method for tasks which must be executed on IO thread */
+ template <class Invokable, class... Args>
+ static void PerformOnIOThread(Invokable&& aInvokable, Args&&... aArgs) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+ auto boundTask =
+ // For c++17, bind is cleaner than tuple for parameter pack forwarding
+ // NOLINTNEXTLINE(modernize-avoid-bind)
+ std::bind(std::forward<Invokable>(aInvokable),
+ std::forward<Args>(aArgs)...);
+ InvokeAsync(quotaManager->IOThread(), __func__,
+ [boundTask = std::move(boundTask)]() {
+ boundTask();
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& value) {
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ }
+
+ static const nsCOMPtr<nsISerialEventTarget>& BackgroundTargetStrongRef() {
+ return sBackgroundTarget;
+ }
+
+ private:
+ static nsCOMPtr<nsISerialEventTarget> sBackgroundTarget;
+};
+
+} // namespace mozilla::dom::quota::test
+
+#endif // DOM_QUOTA_TEST_GTEST_QUOTAMANAGERDEPENDENCYFIXTURE_H_
diff --git a/dom/quota/test/gtest/QuotaTestChild.h b/dom/quota/test/gtest/QuotaTestChild.h
new file mode 100644
index 0000000000..d0d87d8267
--- /dev/null
+++ b/dom/quota/test/gtest/QuotaTestChild.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_QUOTA_TEST_GTEST_QUOTATESTCHILD_H_
+#define DOM_QUOTA_TEST_GTEST_QUOTATESTCHILD_H_
+
+#include "mozilla/dom/quota/PQuotaTestChild.h"
+
+namespace mozilla::dom::quota {
+
+class QuotaTestChild : public PQuotaTestChild {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaTestChild, override)
+
+ private:
+ ~QuotaTestChild() = default;
+};
+
+} // namespace mozilla::dom::quota
+
+#endif // DOM_QUOTA_TEST_GTEST_QUOTATESTCHILD_H_
diff --git a/dom/quota/test/gtest/QuotaTestParent.h b/dom/quota/test/gtest/QuotaTestParent.h
new file mode 100644
index 0000000000..5ec1e128b3
--- /dev/null
+++ b/dom/quota/test/gtest/QuotaTestParent.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_QUOTA_TEST_GTEST_QUOTATESTPARENT_H_
+#define DOM_QUOTA_TEST_GTEST_QUOTATESTPARENT_H_
+
+#include "mozilla/dom/quota/PQuotaTestParent.h"
+
+namespace mozilla::dom::quota {
+
+class QuotaTestParent : public PQuotaTestParent {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaTestParent, override)
+
+ public:
+ mozilla::ipc::IPCResult RecvTry_Success_CustomErr_QmIpcFail(
+ bool* aTryDidNotReturn);
+
+ mozilla::ipc::IPCResult RecvTry_Success_CustomErr_IpcFail(
+ bool* aTryDidNotReturn);
+
+ mozilla::ipc::IPCResult RecvTryInspect_Success_CustomErr_QmIpcFail(
+ bool* aTryDidNotReturn);
+
+ mozilla::ipc::IPCResult RecvTryInspect_Success_CustomErr_IpcFail(
+ bool* aTryDidNotReturn);
+
+ private:
+ ~QuotaTestParent() = default;
+};
+
+} // namespace mozilla::dom::quota
+
+#endif // DOM_QUOTA_TEST_GTEST_QUOTATESTPARENT_H_
diff --git a/dom/quota/test/gtest/TestCheckedUnsafePtr.cpp b/dom/quota/test/gtest/TestCheckedUnsafePtr.cpp
new file mode 100644
index 0000000000..14abb6631d
--- /dev/null
+++ b/dom/quota/test/gtest/TestCheckedUnsafePtr.cpp
@@ -0,0 +1,145 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/quota/CheckedUnsafePtr.h"
+
+#include "gtest/gtest.h"
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include "mozilla/fallible.h"
+
+using namespace mozilla;
+
+class NoCheckTestType
+ : public SupportsCheckedUnsafePtr<DoNotCheckCheckedUnsafePtrs> {};
+
+#if __cplusplus < 202002L
+static_assert(std::is_literal_type_v<CheckedUnsafePtr<NoCheckTestType>>);
+#endif
+
+static_assert(
+ std::is_trivially_copy_constructible_v<CheckedUnsafePtr<NoCheckTestType>>);
+static_assert(
+ std::is_trivially_copy_assignable_v<CheckedUnsafePtr<NoCheckTestType>>);
+static_assert(
+ std::is_trivially_move_constructible_v<CheckedUnsafePtr<NoCheckTestType>>);
+static_assert(
+ std::is_trivially_move_assignable_v<CheckedUnsafePtr<NoCheckTestType>>);
+
+class TestCheckingPolicy : public CheckCheckedUnsafePtrs<TestCheckingPolicy> {
+ protected:
+ explicit TestCheckingPolicy(bool& aPassedCheck)
+ : mPassedCheck(aPassedCheck) {}
+
+ private:
+ friend class mozilla::CheckingPolicyAccess;
+ void NotifyCheckFailure() { mPassedCheck = false; }
+
+ bool& mPassedCheck;
+};
+
+struct BasePointee : public SupportsCheckedUnsafePtr<TestCheckingPolicy> {
+ explicit BasePointee(bool& aCheckPassed)
+ : SupportsCheckedUnsafePtr<TestCheckingPolicy>(aCheckPassed) {}
+};
+
+struct DerivedPointee : public BasePointee {
+ using BasePointee::BasePointee;
+};
+
+class CheckedUnsafePtrTest : public ::testing::Test {
+ protected:
+ bool mPassedCheck = true;
+};
+
+TEST_F(CheckedUnsafePtrTest, PointeeWithNoCheckedUnsafePtrs) {
+ { DerivedPointee pointee{mPassedCheck}; }
+ ASSERT_TRUE(mPassedCheck);
+}
+
+template <typename PointerType>
+class TypedCheckedUnsafePtrTest : public CheckedUnsafePtrTest {};
+
+TYPED_TEST_SUITE_P(TypedCheckedUnsafePtrTest);
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest, PointeeWithOneCheckedUnsafePtr) {
+ {
+ DerivedPointee pointee{this->mPassedCheck};
+ CheckedUnsafePtr<TypeParam> ptr = &pointee;
+ }
+ ASSERT_TRUE(this->mPassedCheck);
+}
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest, CheckedUnsafePtrCopyConstructed) {
+ {
+ DerivedPointee pointee{this->mPassedCheck};
+ CheckedUnsafePtr<TypeParam> ptr1 = &pointee;
+ CheckedUnsafePtr<TypeParam> ptr2 = ptr1;
+ }
+ ASSERT_TRUE(this->mPassedCheck);
+}
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest, CheckedUnsafePtrCopyAssigned) {
+ {
+ DerivedPointee pointee{this->mPassedCheck};
+ CheckedUnsafePtr<TypeParam> ptr1 = &pointee;
+ CheckedUnsafePtr<TypeParam> ptr2;
+ ptr2 = ptr1;
+ }
+ ASSERT_TRUE(this->mPassedCheck);
+}
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest,
+ PointeeWithOneDanglingCheckedUnsafePtr) {
+ [this]() -> CheckedUnsafePtr<TypeParam> {
+ DerivedPointee pointee{this->mPassedCheck};
+ return &pointee;
+ }();
+ ASSERT_FALSE(this->mPassedCheck);
+}
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest,
+ PointeeWithOneCopiedDanglingCheckedUnsafePtr) {
+ const auto dangling1 = [this]() -> CheckedUnsafePtr<DerivedPointee> {
+ DerivedPointee pointee{this->mPassedCheck};
+ return &pointee;
+ }();
+ EXPECT_FALSE(this->mPassedCheck);
+
+ // With AddressSanitizer we would hopefully detect if the copy constructor
+ // tries to add dangling2 to the now-gone pointee's unsafe pointer array. No
+ // promises though, since it might be optimized away.
+ CheckedUnsafePtr<TypeParam> dangling2{dangling1};
+ ASSERT_TRUE(dangling2);
+}
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest,
+ PointeeWithOneCopyAssignedDanglingCheckedUnsafePtr) {
+ const auto dangling1 = [this]() -> CheckedUnsafePtr<DerivedPointee> {
+ DerivedPointee pointee{this->mPassedCheck};
+ return &pointee;
+ }();
+ EXPECT_FALSE(this->mPassedCheck);
+
+ // With AddressSanitizer we would hopefully detect if the assignment tries to
+ // add dangling2 to the now-gone pointee's unsafe pointer array. No promises
+ // though, since it might be optimized away.
+ CheckedUnsafePtr<TypeParam> dangling2;
+ dangling2 = dangling1;
+ ASSERT_TRUE(dangling2);
+}
+
+REGISTER_TYPED_TEST_SUITE_P(TypedCheckedUnsafePtrTest,
+ PointeeWithOneCheckedUnsafePtr,
+ CheckedUnsafePtrCopyConstructed,
+ CheckedUnsafePtrCopyAssigned,
+ PointeeWithOneDanglingCheckedUnsafePtr,
+ PointeeWithOneCopiedDanglingCheckedUnsafePtr,
+ PointeeWithOneCopyAssignedDanglingCheckedUnsafePtr);
+
+using BothTypes = ::testing::Types<BasePointee, DerivedPointee>;
+INSTANTIATE_TYPED_TEST_SUITE_P(InstantiationOf, TypedCheckedUnsafePtrTest,
+ BothTypes);
diff --git a/dom/quota/test/gtest/TestClientUsageArray.cpp b/dom/quota/test/gtest/TestClientUsageArray.cpp
new file mode 100644
index 0000000000..f5db984ccd
--- /dev/null
+++ b/dom/quota/test/gtest/TestClientUsageArray.cpp
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ClientUsageArray.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla::dom::quota;
+
+TEST(DOM_Quota_ClientUsageArray, Deserialize)
+{
+ ClientUsageArray clientUsages;
+ nsresult rv = clientUsages.Deserialize("I872215 C8404073805 L161709"_ns);
+ ASSERT_EQ(rv, NS_OK);
+}
diff --git a/dom/quota/test/gtest/TestEncryptedStream.cpp b/dom/quota/test/gtest/TestEncryptedStream.cpp
new file mode 100644
index 0000000000..9eac04a391
--- /dev/null
+++ b/dom/quota/test/gtest/TestEncryptedStream.cpp
@@ -0,0 +1,791 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstdlib>
+#include <new>
+#include <numeric>
+#include <ostream>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "ErrorList.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FixedBufferOutputStream.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/SafeRefPtr.h"
+#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
+#include "mozilla/dom/quota/DummyCipherStrategy.h"
+#include "mozilla/dom/quota/EncryptedBlock.h"
+#include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
+#include "mozilla/dom/quota/NSSCipherStrategy.h"
+#include "mozilla/fallible.h"
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsICloneableInputStream.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsISupports.h"
+#include "nsITellableStream.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nscore.h"
+#include "nss.h"
+
+namespace mozilla::dom::quota {
+
+// Similar to ArrayBufferInputStream from netwerk/base/ArrayBufferInputStream.h,
+// but this is initialized from a Span on construction, rather than lazily from
+// a JS ArrayBuffer.
+class ArrayBufferInputStream : public nsIInputStream,
+ public nsISeekableStream,
+ public nsICloneableInputStream {
+ public:
+ explicit ArrayBufferInputStream(mozilla::Span<const uint8_t> aData);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSITELLABLESTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+
+ private:
+ virtual ~ArrayBufferInputStream() = default;
+
+ mozilla::UniquePtr<char[]> mArrayBuffer;
+ uint32_t mBufferLength;
+ uint32_t mPos;
+ bool mClosed;
+};
+
+NS_IMPL_ADDREF(ArrayBufferInputStream);
+NS_IMPL_RELEASE(ArrayBufferInputStream);
+
+NS_INTERFACE_MAP_BEGIN(ArrayBufferInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+ NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+ArrayBufferInputStream::ArrayBufferInputStream(
+ mozilla::Span<const uint8_t> aData)
+ : mArrayBuffer(MakeUnique<char[]>(aData.Length())),
+ mBufferLength(aData.Length()),
+ mPos(0),
+ mClosed(false) {
+ std::copy(aData.cbegin(), aData.cend(), mArrayBuffer.get());
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Close() {
+ mClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Available(uint64_t* aCount) {
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (mArrayBuffer) {
+ *aCount = mBufferLength ? mBufferLength - mPos : 0;
+ } else {
+ *aCount = 0;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::StreamStatus() {
+ return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aReadCount) {
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t aCount, uint32_t* result) {
+ MOZ_ASSERT(result, "null ptr");
+ MOZ_ASSERT(mBufferLength >= mPos, "bad stream state");
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength),
+ "stream inited incorrectly");
+
+ *result = 0;
+ while (mPos < mBufferLength) {
+ uint32_t remaining = mBufferLength - mPos;
+ MOZ_ASSERT(mArrayBuffer);
+
+ uint32_t count = std::min(aCount, remaining);
+ if (count == 0) {
+ break;
+ }
+
+ uint32_t written;
+ nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count,
+ &written);
+ if (NS_FAILED(rv)) {
+ // InputStreams do not propagate errors to caller.
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(written <= count,
+ "writer should not write more than we asked it to write");
+ mPos += written;
+ *result += written;
+ aCount -= written;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) {
+ // Actually, the stream never blocks, but we lie about it because of the
+ // assumptions in DecryptingInputStream.
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ArrayBufferInputStream::Tell(int64_t* const aRetval) {
+ MOZ_ASSERT(aRetval);
+
+ *aRetval = mPos;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP ArrayBufferInputStream::Seek(const int32_t aWhence,
+ const int64_t aOffset) {
+ // XXX This is not safe. it's hard to use CheckedInt here, though. As long as
+ // the class is only used for testing purposes, that's probably fine.
+
+ int32_t newPos = mPos;
+ switch (aWhence) {
+ case NS_SEEK_SET:
+ newPos = aOffset;
+ break;
+ case NS_SEEK_CUR:
+ newPos += aOffset;
+ break;
+ case NS_SEEK_END:
+ newPos = mBufferLength;
+ newPos += aOffset;
+ break;
+ default:
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (newPos < 0 || static_cast<uint32_t>(newPos) > mBufferLength) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ mPos = newPos;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP ArrayBufferInputStream::SetEOF() {
+ // Truncating is not supported on a read-only stream.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ArrayBufferInputStream::GetCloneable(bool* aCloneable) {
+ *aCloneable = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ArrayBufferInputStream::Clone(nsIInputStream** _retval) {
+ *_retval = MakeAndAddRef<ArrayBufferInputStream>(
+ AsBytes(Span{mArrayBuffer.get(), mBufferLength}))
+ .take();
+
+ return NS_OK;
+}
+} // namespace mozilla::dom::quota
+
+namespace mozilla {
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedNSSContext, NSSInitContext,
+ NSS_ShutdownContext);
+
+} // namespace mozilla
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+class DOM_Quota_EncryptedStream : public ::testing::Test {
+ public:
+ static void SetUpTestCase() {
+ // Do this only once, do not tear it down per test case.
+ if (!sNssContext) {
+ sNssContext =
+ NSS_InitContext("", "", "", "", nullptr,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_OPTIMIZESPACE | NSS_INIT_NOROOTINIT);
+ }
+ }
+
+ static void TearDownTestCase() { sNssContext = nullptr; }
+
+ private:
+ inline static ScopedNSSContext sNssContext = ScopedNSSContext{};
+};
+
+enum struct FlushMode { AfterEachChunk, Never };
+enum struct ChunkSize { SingleByte, Unaligned, DataSize };
+
+using PackedTestParams =
+ std::tuple<size_t, ChunkSize, ChunkSize, size_t, FlushMode>;
+
+static size_t EffectiveChunkSize(const ChunkSize aChunkSize,
+ const size_t aDataSize) {
+ switch (aChunkSize) {
+ case ChunkSize::SingleByte:
+ return 1;
+ case ChunkSize::Unaligned:
+ return 17;
+ case ChunkSize::DataSize:
+ return aDataSize;
+ }
+ MOZ_CRASH("Unknown ChunkSize");
+}
+
+struct TestParams {
+ MOZ_IMPLICIT constexpr TestParams(const PackedTestParams& aPackedParams)
+ : mDataSize(std::get<0>(aPackedParams)),
+ mWriteChunkSize(std::get<1>(aPackedParams)),
+ mReadChunkSize(std::get<2>(aPackedParams)),
+ mBlockSize(std::get<3>(aPackedParams)),
+ mFlushMode(std::get<4>(aPackedParams)) {}
+
+ constexpr size_t DataSize() const { return mDataSize; }
+
+ size_t EffectiveWriteChunkSize() const {
+ return EffectiveChunkSize(mWriteChunkSize, mDataSize);
+ }
+
+ size_t EffectiveReadChunkSize() const {
+ return EffectiveChunkSize(mReadChunkSize, mDataSize);
+ }
+
+ constexpr size_t BlockSize() const { return mBlockSize; }
+
+ constexpr enum FlushMode FlushMode() const { return mFlushMode; }
+
+ private:
+ size_t mDataSize;
+
+ ChunkSize mWriteChunkSize;
+ ChunkSize mReadChunkSize;
+
+ size_t mBlockSize;
+ enum FlushMode mFlushMode;
+};
+
+std::string TestParamToString(
+ const testing::TestParamInfo<PackedTestParams>& aTestParams) {
+ const TestParams& testParams = aTestParams.param;
+
+ static constexpr char kSeparator[] = "_";
+
+ std::stringstream ss;
+ ss << "data" << testParams.DataSize() << kSeparator << "writechunk"
+ << testParams.EffectiveWriteChunkSize() << kSeparator << "readchunk"
+ << testParams.EffectiveReadChunkSize() << kSeparator << "block"
+ << testParams.BlockSize() << kSeparator;
+ switch (testParams.FlushMode()) {
+ case FlushMode::Never:
+ ss << "FlushNever";
+ break;
+ case FlushMode::AfterEachChunk:
+ ss << "FlushAfterEachChunk";
+ break;
+ };
+ return ss.str();
+}
+
+class ParametrizedCryptTest
+ : public DOM_Quota_EncryptedStream,
+ public testing::WithParamInterface<PackedTestParams> {};
+
+static auto MakeTestData(const size_t aDataSize) {
+ auto data = nsTArray<uint8_t>();
+ data.SetLength(aDataSize);
+ std::iota(data.begin(), data.end(), 0);
+ return data;
+}
+
+template <typename CipherStrategy>
+static void WriteTestData(nsCOMPtr<nsIOutputStream>&& aBaseOutputStream,
+ const Span<const uint8_t> aData,
+ const size_t aWriteChunkSize, const size_t aBlockSize,
+ const typename CipherStrategy::KeyType& aKey,
+ const FlushMode aFlushMode) {
+ auto outStream = MakeSafeRefPtr<EncryptingOutputStream<CipherStrategy>>(
+ std::move(aBaseOutputStream), aBlockSize, aKey);
+
+ for (auto remaining = aData; !remaining.IsEmpty();) {
+ auto [currentChunk, newRemaining] =
+ remaining.SplitAt(std::min(aWriteChunkSize, remaining.Length()));
+ remaining = newRemaining;
+
+ uint32_t written;
+ EXPECT_EQ(NS_OK, outStream->Write(
+ reinterpret_cast<const char*>(currentChunk.Elements()),
+ currentChunk.Length(), &written));
+ EXPECT_EQ(currentChunk.Length(), written);
+
+ if (aFlushMode == FlushMode::AfterEachChunk) {
+ outStream->Flush();
+ }
+ }
+
+ // Close explicitly so we can check the result.
+ EXPECT_EQ(NS_OK, outStream->Close());
+}
+
+template <typename CipherStrategy>
+static void NoExtraChecks(DecryptingInputStream<CipherStrategy>& aInputStream,
+ Span<const uint8_t> aExpectedData,
+ Span<const uint8_t> aRemainder) {}
+
+template <typename CipherStrategy,
+ typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)>
+static void ReadTestData(
+ DecryptingInputStream<CipherStrategy>& aDecryptingInputStream,
+ const Span<const uint8_t> aExpectedData, const size_t aReadChunkSize,
+ const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) {
+ auto readData = nsTArray<uint8_t>();
+ readData.SetLength(aReadChunkSize);
+ for (auto remainder = aExpectedData; !remainder.IsEmpty();) {
+ auto [currentExpected, newExpectedRemainder] =
+ remainder.SplitAt(std::min(aReadChunkSize, remainder.Length()));
+ remainder = newExpectedRemainder;
+
+ uint32_t read;
+ EXPECT_EQ(NS_OK, aDecryptingInputStream.Read(
+ reinterpret_cast<char*>(readData.Elements()),
+ currentExpected.Length(), &read));
+ EXPECT_EQ(currentExpected.Length(), read);
+ EXPECT_EQ(currentExpected,
+ Span{readData}.First(currentExpected.Length()).AsConst());
+
+ aExtraChecks(aDecryptingInputStream, aExpectedData, remainder);
+ }
+
+ // Expect EOF.
+ uint32_t read;
+ EXPECT_EQ(NS_OK, aDecryptingInputStream.Read(
+ reinterpret_cast<char*>(readData.Elements()),
+ readData.Length(), &read));
+ EXPECT_EQ(0u, read);
+}
+
+template <typename CipherStrategy,
+ typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)>
+static auto ReadTestData(
+ MovingNotNull<nsCOMPtr<nsIInputStream>>&& aBaseInputStream,
+ const Span<const uint8_t> aExpectedData, const size_t aReadChunkSize,
+ const size_t aBlockSize, const typename CipherStrategy::KeyType& aKey,
+ const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) {
+ auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>(
+ std::move(aBaseInputStream), aBlockSize, aKey);
+
+ ReadTestData(*inStream, aExpectedData, aReadChunkSize, aExtraChecks);
+
+ return inStream;
+}
+
+// XXX Change to return the buffer instead.
+template <typename CipherStrategy,
+ typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)>
+static RefPtr<FixedBufferOutputStream> DoRoundtripTest(
+ const size_t aDataSize, const size_t aWriteChunkSize,
+ const size_t aReadChunkSize, const size_t aBlockSize,
+ const typename CipherStrategy::KeyType& aKey, const FlushMode aFlushMode,
+ const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) {
+ // XXX Add deduction guide for RefPtr from already_AddRefed
+ const auto baseOutputStream = WrapNotNull(
+ RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)});
+
+ const auto data = MakeTestData(aDataSize);
+
+ WriteTestData<CipherStrategy>(
+ nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data},
+ aWriteChunkSize, aBlockSize, aKey, aFlushMode);
+
+ const auto baseInputStream =
+ MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData());
+
+ ReadTestData<CipherStrategy>(
+ WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), Span{data},
+ aReadChunkSize, aBlockSize, aKey, aExtraChecks);
+
+ return baseOutputStream;
+}
+
+TEST_P(ParametrizedCryptTest, NSSCipherStrategy) {
+ using CipherStrategy = NSSCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ auto keyOrErr = CipherStrategy::GenerateKey();
+ ASSERT_FALSE(keyOrErr.isErr());
+
+ DoRoundtripTest<CipherStrategy>(
+ testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
+ testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
+ keyOrErr.unwrap(), testParams.FlushMode());
+}
+
+TEST_P(ParametrizedCryptTest, DummyCipherStrategy_CheckOutput) {
+ using CipherStrategy = DummyCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ const auto encryptedDataStream = DoRoundtripTest<CipherStrategy>(
+ testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
+ testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
+ CipherStrategy::KeyType{}, testParams.FlushMode());
+
+ if (HasFailure()) {
+ return;
+ }
+
+ const auto encryptedData = encryptedDataStream->WrittenData();
+ const auto encryptedDataSpan = AsBytes(Span(encryptedData));
+
+ const auto plainTestData = MakeTestData(testParams.DataSize());
+ auto encryptedBlock = EncryptedBlock<DummyCipherStrategy::BlockPrefixLength,
+ DummyCipherStrategy::BasicBlockSize>{
+ testParams.BlockSize(),
+ };
+ for (auto [encryptedRemainder, plainRemainder] =
+ std::pair(encryptedDataSpan, Span(plainTestData));
+ !encryptedRemainder.IsEmpty();) {
+ const auto [currentBlock, newEncryptedRemainder] =
+ encryptedRemainder.SplitAt(testParams.BlockSize());
+ encryptedRemainder = newEncryptedRemainder;
+
+ std::copy(currentBlock.cbegin(), currentBlock.cend(),
+ encryptedBlock.MutableWholeBlock().begin());
+
+ ASSERT_FALSE(plainRemainder.IsEmpty());
+ const auto [currentPlain, newPlainRemainder] =
+ plainRemainder.SplitAt(encryptedBlock.ActualPayloadLength());
+ plainRemainder = newPlainRemainder;
+
+ const auto pseudoIV = encryptedBlock.CipherPrefix();
+ const auto payload = encryptedBlock.Payload();
+
+ EXPECT_EQ(Span(DummyCipherStrategy::MakeBlockPrefix()), pseudoIV);
+
+ auto untransformedPayload = nsTArray<uint8_t>();
+ untransformedPayload.SetLength(testParams.BlockSize());
+ DummyCipherStrategy::DummyTransform(payload, untransformedPayload);
+
+ EXPECT_EQ(
+ currentPlain,
+ Span(untransformedPayload).AsConst().First(currentPlain.Length()));
+ }
+}
+
+TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Tell) {
+ using CipherStrategy = DummyCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ DoRoundtripTest<CipherStrategy>(
+ testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
+ testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
+ CipherStrategy::KeyType{}, testParams.FlushMode(),
+ [](auto& inStream, Span<const uint8_t> expectedData,
+ Span<const uint8_t> remainder) {
+ // Check that Tell tells the right position.
+ int64_t pos;
+ EXPECT_EQ(NS_OK, inStream.Tell(&pos));
+ EXPECT_EQ(expectedData.Length() - remainder.Length(),
+ static_cast<uint64_t>(pos));
+ });
+}
+
+TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Available) {
+ using CipherStrategy = DummyCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ DoRoundtripTest<CipherStrategy>(
+ testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
+ testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
+ CipherStrategy::KeyType{}, testParams.FlushMode(),
+ [](auto& inStream, Span<const uint8_t> expectedData,
+ Span<const uint8_t> remainder) {
+ // Check that Available tells the right remainder.
+ uint64_t available;
+ EXPECT_EQ(NS_OK, inStream.Available(&available));
+ EXPECT_EQ(remainder.Length(), available);
+ });
+}
+
+TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Clone) {
+ using CipherStrategy = DummyCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ // XXX Add deduction guide for RefPtr from already_AddRefed
+ const auto baseOutputStream = WrapNotNull(
+ RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)});
+
+ const auto data = MakeTestData(testParams.DataSize());
+
+ WriteTestData<CipherStrategy>(
+ nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data},
+ testParams.EffectiveWriteChunkSize(), testParams.BlockSize(),
+ CipherStrategy::KeyType{}, testParams.FlushMode());
+
+ const auto baseInputStream =
+ MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData());
+
+ const auto inStream = ReadTestData<CipherStrategy>(
+ WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), Span{data},
+ testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
+ CipherStrategy::KeyType{});
+
+ nsCOMPtr<nsIInputStream> clonedInputStream;
+ EXPECT_EQ(NS_OK, inStream->Clone(getter_AddRefs(clonedInputStream)));
+
+ ReadTestData(
+ static_cast<DecryptingInputStream<CipherStrategy>&>(*clonedInputStream),
+ Span{data}, testParams.EffectiveReadChunkSize());
+}
+
+// XXX This test is actually only parametrized on the block size.
+TEST_P(ParametrizedCryptTest, DummyCipherStrategy_IncompleteBlock) {
+ using CipherStrategy = DummyCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ // Provide half a block, content doesn't matter.
+ nsTArray<uint8_t> data;
+ data.SetLength(testParams.BlockSize() / 2);
+
+ const auto baseInputStream = MakeRefPtr<ArrayBufferInputStream>(data);
+
+ const auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>(
+ WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}),
+ testParams.BlockSize(), CipherStrategy::KeyType{});
+
+ nsTArray<uint8_t> readData;
+ readData.SetLength(testParams.BlockSize());
+ uint32_t read;
+ EXPECT_EQ(NS_ERROR_CORRUPTED_CONTENT,
+ inStream->Read(reinterpret_cast<char*>(readData.Elements()),
+ readData.Length(), &read));
+}
+
+enum struct SeekOffset {
+ Zero,
+ MinusHalfDataSize,
+ PlusHalfDataSize,
+ PlusDataSize,
+ MinusDataSize
+};
+using SeekOp = std::pair<int32_t, SeekOffset>;
+
+using PackedSeekTestParams = std::tuple<size_t, size_t, std::vector<SeekOp>>;
+
+struct SeekTestParams {
+ size_t mDataSize;
+ size_t mBlockSize;
+ std::vector<SeekOp> mSeekOps;
+
+ MOZ_IMPLICIT SeekTestParams(const PackedSeekTestParams& aPackedParams)
+ : mDataSize(std::get<0>(aPackedParams)),
+ mBlockSize(std::get<1>(aPackedParams)),
+ mSeekOps(std::get<2>(aPackedParams)) {}
+};
+
+std::string SeekTestParamToString(
+ const testing::TestParamInfo<PackedSeekTestParams>& aTestParams) {
+ const SeekTestParams& testParams = aTestParams.param;
+
+ static constexpr char kSeparator[] = "_";
+
+ std::stringstream ss;
+ ss << "data" << testParams.mDataSize << kSeparator << "writechunk"
+ << testParams.mBlockSize << kSeparator;
+ for (const auto& seekOp : testParams.mSeekOps) {
+ switch (seekOp.first) {
+ case nsISeekableStream::NS_SEEK_SET:
+ ss << "Set";
+ break;
+ case nsISeekableStream::NS_SEEK_CUR:
+ ss << "Cur";
+ break;
+ case nsISeekableStream::NS_SEEK_END:
+ ss << "End";
+ break;
+ };
+ switch (seekOp.second) {
+ case SeekOffset::Zero:
+ ss << "Zero";
+ break;
+ case SeekOffset::MinusHalfDataSize:
+ ss << "MinusHalfDataSize";
+ break;
+ case SeekOffset::PlusHalfDataSize:
+ ss << "PlusHalfDataSize";
+ break;
+ case SeekOffset::MinusDataSize:
+ ss << "MinusDataSize";
+ break;
+ case SeekOffset::PlusDataSize:
+ ss << "PlusDataSize";
+ break;
+ };
+ }
+ return ss.str();
+}
+
+class ParametrizedSeekCryptTest
+ : public DOM_Quota_EncryptedStream,
+ public testing::WithParamInterface<PackedSeekTestParams> {};
+
+TEST_P(ParametrizedSeekCryptTest, DummyCipherStrategy_Seek) {
+ using CipherStrategy = DummyCipherStrategy;
+ const SeekTestParams& testParams = GetParam();
+
+ const auto baseOutputStream = WrapNotNull(
+ RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)});
+
+ const auto data = MakeTestData(testParams.mDataSize);
+
+ WriteTestData<CipherStrategy>(
+ nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data},
+ testParams.mDataSize, testParams.mBlockSize, CipherStrategy::KeyType{},
+ FlushMode::Never);
+
+ const auto baseInputStream =
+ MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData());
+
+ const auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>(
+ WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}),
+ testParams.mBlockSize, CipherStrategy::KeyType{});
+
+ uint32_t accumulatedOffset = 0;
+ for (const auto& seekOp : testParams.mSeekOps) {
+ const auto offset = [offsetKind = seekOp.second,
+ dataSize = testParams.mDataSize]() -> int64_t {
+ switch (offsetKind) {
+ case SeekOffset::Zero:
+ return 0;
+ case SeekOffset::MinusHalfDataSize:
+ return -static_cast<int64_t>(dataSize) / 2;
+ case SeekOffset::PlusHalfDataSize:
+ return dataSize / 2;
+ case SeekOffset::MinusDataSize:
+ return -static_cast<int64_t>(dataSize);
+ case SeekOffset::PlusDataSize:
+ return dataSize;
+ }
+ MOZ_CRASH("Unknown SeekOffset");
+ }();
+ switch (seekOp.first) {
+ case nsISeekableStream::NS_SEEK_SET:
+ accumulatedOffset = offset;
+ break;
+ case nsISeekableStream::NS_SEEK_CUR:
+ accumulatedOffset += offset;
+ break;
+ case nsISeekableStream::NS_SEEK_END:
+ accumulatedOffset = testParams.mDataSize + offset;
+ break;
+ }
+ EXPECT_EQ(NS_OK, inStream->Seek(seekOp.first, offset));
+ }
+
+ {
+ int64_t actualOffset;
+ EXPECT_EQ(NS_OK, inStream->Tell(&actualOffset));
+
+ EXPECT_EQ(actualOffset, accumulatedOffset);
+ }
+
+ auto readData = nsTArray<uint8_t>();
+ readData.SetLength(data.Length());
+ uint32_t read;
+ EXPECT_EQ(NS_OK, inStream->Read(reinterpret_cast<char*>(readData.Elements()),
+ readData.Length(), &read));
+ // XXX Or should 'read' indicate the actual number of bytes read,
+ // including the encryption overhead?
+ EXPECT_EQ(testParams.mDataSize - accumulatedOffset, read);
+ EXPECT_EQ(Span{data}.SplitAt(accumulatedOffset).second,
+ Span{readData}.First(read).AsConst());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ DOM_Quota_EncryptedStream_Parametrized, ParametrizedCryptTest,
+ testing::Combine(
+ /* dataSize */ testing::Values(0u, 16u, 256u, 512u, 513u),
+ /* writeChunkSize */
+ testing::Values(ChunkSize::SingleByte, ChunkSize::Unaligned,
+ ChunkSize::DataSize),
+ /* readChunkSize */
+ testing::Values(ChunkSize::SingleByte, ChunkSize::Unaligned,
+ ChunkSize::DataSize),
+ /* blockSize */ testing::Values(256u, 1024u /*, 8192u*/),
+ /* flushMode */
+ testing::Values(FlushMode::Never, FlushMode::AfterEachChunk)),
+ TestParamToString);
+
+INSTANTIATE_TEST_SUITE_P(
+ DOM_IndexedDB_EncryptedStream_ParametrizedSeek, ParametrizedSeekCryptTest,
+ testing::Combine(
+ /* dataSize */ testing::Values(0u, 16u, 256u, 512u, 513u),
+ /* blockSize */ testing::Values(256u, 1024u /*, 8192u*/),
+ /* seekOperations */
+ testing::Values(/* NS_SEEK_SET only, single ops */
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_SET,
+ SeekOffset::PlusDataSize}},
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_SET,
+ SeekOffset::PlusHalfDataSize}},
+ /* NS_SEEK_SET only, multiple ops */
+ std::vector<SeekOp>{
+ {nsISeekableStream::NS_SEEK_SET,
+ SeekOffset::PlusHalfDataSize},
+ {nsISeekableStream::NS_SEEK_SET, SeekOffset::Zero}},
+ /* NS_SEEK_CUR only, single ops */
+ std::vector<SeekOp>{
+ {nsISeekableStream::NS_SEEK_CUR, SeekOffset::Zero}},
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR,
+ SeekOffset::PlusDataSize}},
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR,
+ SeekOffset::PlusHalfDataSize}},
+ /* NS_SEEK_END only, single ops */
+ std::vector<SeekOp>{
+ {nsISeekableStream::NS_SEEK_END, SeekOffset::Zero}},
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END,
+ SeekOffset::MinusDataSize}},
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END,
+ SeekOffset::MinusHalfDataSize}})),
+ SeekTestParamToString);
diff --git a/dom/quota/test/gtest/TestFileOutputStream.cpp b/dom/quota/test/gtest/TestFileOutputStream.cpp
new file mode 100644
index 0000000000..d5a6095094
--- /dev/null
+++ b/dom/quota/test/gtest/TestFileOutputStream.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/quota/Client.h"
+#include "mozilla/dom/quota/CommonMetadata.h"
+#include "mozilla/dom/quota/FileStreams.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "QuotaManagerDependencyFixture.h"
+
+namespace mozilla::dom::quota::test {
+
+class TestFileOutputStream : public QuotaManagerDependencyFixture {
+ public:
+ static void SetUpTestCase() {
+ ASSERT_NO_FATAL_FAILURE(InitializeFixture());
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ prefs->SetIntPref("dom.quotaManager.temporaryStorage.fixedLimit",
+ mQuotaLimit);
+ }
+
+ static void TearDownTestCase() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ prefs->ClearUserPref("dom.quotaManager.temporaryStorage.fixedLimit");
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownFixture());
+ }
+
+ static const int32_t mQuotaLimit = 8192;
+};
+
+TEST_F(TestFileOutputStream, extendFileStreamWithSetEOF) {
+ auto ioTask = []() {
+ quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
+
+ auto originMetadata =
+ quota::OriginMetadata{""_ns,
+ "example.com"_ns,
+ "http://example.com"_ns,
+ "http://example.com"_ns,
+ /* aIsPrivate */ false,
+ quota::PERSISTENCE_TYPE_DEFAULT};
+
+ {
+ ASSERT_NS_SUCCEEDED(quotaManager->EnsureStorageIsInitialized());
+
+ ASSERT_NS_SUCCEEDED(quotaManager->EnsureTemporaryStorageIsInitialized());
+
+ auto res = quotaManager->EnsureTemporaryOriginIsInitialized(
+ quota::PERSISTENCE_TYPE_DEFAULT, originMetadata);
+ ASSERT_TRUE(res.isOk());
+ }
+
+ const int64_t groupLimit =
+ static_cast<int64_t>(quotaManager->GetGroupLimit());
+ ASSERT_TRUE(mQuotaLimit * 1024LL == groupLimit);
+
+ // We don't use the tested stream itself to check the file size as it
+ // may report values which have not been written to disk.
+ RefPtr<quota::FileOutputStream> check = MakeRefPtr<quota::FileOutputStream>(
+ quota::PERSISTENCE_TYPE_DEFAULT, originMetadata,
+ quota::Client::Type::SDB);
+
+ RefPtr<quota::FileOutputStream> stream =
+ MakeRefPtr<quota::FileOutputStream>(quota::PERSISTENCE_TYPE_DEFAULT,
+ originMetadata,
+ quota::Client::Type::SDB);
+
+ {
+ auto testPathRes = quotaManager->GetOriginDirectory(originMetadata);
+
+ ASSERT_TRUE(testPathRes.isOk());
+
+ nsCOMPtr<nsIFile> testPath = testPathRes.unwrap();
+
+ ASSERT_NS_SUCCEEDED(testPath->AppendRelativePath(u"sdb"_ns));
+
+ ASSERT_NS_SUCCEEDED(
+ testPath->AppendRelativePath(u"tTestFileOutputStream.txt"_ns));
+
+ bool exists = true;
+ ASSERT_NS_SUCCEEDED(testPath->Exists(&exists));
+
+ if (exists) {
+ ASSERT_NS_SUCCEEDED(testPath->Remove(/* recursive */ false));
+ }
+
+ ASSERT_NS_SUCCEEDED(testPath->Exists(&exists));
+ ASSERT_FALSE(exists);
+
+ ASSERT_NS_SUCCEEDED(testPath->Create(nsIFile::NORMAL_FILE_TYPE, 0666));
+
+ ASSERT_NS_SUCCEEDED(testPath->Exists(&exists));
+ ASSERT_TRUE(exists);
+
+ nsCOMPtr<nsIFile> checkPath;
+ ASSERT_NS_SUCCEEDED(testPath->Clone(getter_AddRefs(checkPath)));
+
+ const int32_t IOFlags = -1;
+ const int32_t perm = -1;
+ const int32_t behaviorFlags = 0;
+ ASSERT_NS_SUCCEEDED(stream->Init(testPath, IOFlags, perm, behaviorFlags));
+
+ ASSERT_NS_SUCCEEDED(check->Init(testPath, IOFlags, perm, behaviorFlags));
+ }
+
+ // Check that we start with an empty file
+ int64_t avail = 42;
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(0 == avail);
+
+ // Enlarge the file
+ const int64_t toSize = groupLimit;
+ ASSERT_NS_SUCCEEDED(stream->Seek(nsISeekableStream::NS_SEEK_SET, toSize));
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(0 == avail);
+
+ ASSERT_NS_SUCCEEDED(stream->SetEOF());
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toSize == avail);
+
+ // Try to enlarge the file past the limit
+ const int64_t overGroupLimit = groupLimit + 1;
+
+ // Seeking is allowed
+ ASSERT_NS_SUCCEEDED(
+ stream->Seek(nsISeekableStream::NS_SEEK_SET, overGroupLimit));
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toSize == avail);
+
+ // Setting file size to exceed quota should yield no device space error
+ ASSERT_TRUE(NS_ERROR_FILE_NO_DEVICE_SPACE == stream->SetEOF());
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toSize == avail);
+
+ // Shrink the file
+ const int64_t toHalfSize = toSize / 2;
+ ASSERT_NS_SUCCEEDED(
+ stream->Seek(nsISeekableStream::NS_SEEK_SET, toHalfSize));
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toSize == avail);
+
+ ASSERT_NS_SUCCEEDED(stream->SetEOF());
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toHalfSize == avail);
+
+ // Shrink the file back to nothing
+ ASSERT_NS_SUCCEEDED(stream->Seek(nsISeekableStream::NS_SEEK_SET, 0));
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toHalfSize == avail);
+
+ ASSERT_NS_SUCCEEDED(stream->SetEOF());
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(0 == avail);
+ };
+
+ PerformOnIOThread(std::move(ioTask));
+}
+
+} // namespace mozilla::dom::quota::test
diff --git a/dom/quota/test/gtest/TestFlatten.cpp b/dom/quota/test/gtest/TestFlatten.cpp
new file mode 100644
index 0000000000..5ca7675887
--- /dev/null
+++ b/dom/quota/test/gtest/TestFlatten.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Flatten.h"
+
+#include "gtest/gtest.h"
+
+#include "mozilla/Unused.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom::quota {
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wunreachable-code-loop-increment"
+#endif
+TEST(Flatten, FlatEmpty)
+{
+ for (const auto& item : Flatten<int>(nsTArray<int>{})) {
+ Unused << item;
+ FAIL();
+ }
+}
+
+TEST(Flatten, NestedOuterEmpty)
+{
+ for (const auto& item : Flatten<int>(nsTArray<CopyableTArray<int>>{})) {
+ Unused << item;
+ FAIL();
+ }
+}
+
+TEST(Flatten, NestedInnerEmpty)
+{
+ for (const auto& item :
+ Flatten<int>(nsTArray<CopyableTArray<int>>{CopyableTArray<int>{}})) {
+ Unused << item;
+ FAIL();
+ }
+}
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+TEST(Flatten, NestedInnerSingular)
+{
+ nsTArray<int> flattened;
+ for (const auto& item :
+ Flatten<int>(nsTArray<CopyableTArray<int>>{CopyableTArray<int>{1}})) {
+ flattened.AppendElement(item);
+ }
+
+ EXPECT_EQ(nsTArray{1}, flattened);
+}
+
+TEST(Flatten, NestedInnerSingulars)
+{
+ nsTArray<int> flattened;
+ for (const auto& item : Flatten<int>(nsTArray<CopyableTArray<int>>{
+ CopyableTArray<int>{1}, CopyableTArray<int>{2}})) {
+ flattened.AppendElement(item);
+ }
+
+ EXPECT_EQ((nsTArray<int>{{1, 2}}), flattened);
+}
+
+TEST(Flatten, NestedInnerNonSingulars)
+{
+ nsTArray<int> flattened;
+ for (const auto& item : Flatten<int>(nsTArray<CopyableTArray<int>>{
+ CopyableTArray<int>{1, 2}, CopyableTArray<int>{3, 4}})) {
+ flattened.AppendElement(item);
+ }
+
+ EXPECT_EQ((nsTArray<int>{{1, 2, 3, 4}}), flattened);
+}
+
+} // namespace mozilla::dom::quota
diff --git a/dom/quota/test/gtest/TestForwardDecls.cpp b/dom/quota/test/gtest/TestForwardDecls.cpp
new file mode 100644
index 0000000000..223f83af03
--- /dev/null
+++ b/dom/quota/test/gtest/TestForwardDecls.cpp
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <type_traits>
+#include "mozilla/dom/quota/ForwardDecls.h"
+
+using namespace mozilla;
+
+static_assert(std::is_same_v<OkOrErr, Result<Ok, QMResult>>);
diff --git a/dom/quota/test/gtest/TestPersistenceType.cpp b/dom/quota/test/gtest/TestPersistenceType.cpp
new file mode 100644
index 0000000000..c4f1e5d581
--- /dev/null
+++ b/dom/quota/test/gtest/TestPersistenceType.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/quota/PersistenceType.h"
+
+#include "gtest/gtest.h"
+
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+
+namespace mozilla::dom::quota {
+
+TEST(PersistenceType, FromFile)
+{
+ nsCOMPtr<nsIFile> base;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base));
+ EXPECT_EQ(rv, NS_OK);
+
+ const auto testPersistenceType = [&base](const nsLiteralString& aString,
+ const Maybe<PersistenceType> aType) {
+ nsCOMPtr<nsIFile> file;
+
+ nsresult rv = base->Clone(getter_AddRefs(file));
+ EXPECT_EQ(rv, NS_OK);
+
+ rv = file->Append(aString);
+ EXPECT_EQ(rv, NS_OK);
+
+ auto maybePersistenceType = PersistenceTypeFromFile(*file, fallible);
+ EXPECT_EQ(maybePersistenceType, aType);
+ };
+
+ testPersistenceType(u"permanent"_ns, Some(PERSISTENCE_TYPE_PERSISTENT));
+ testPersistenceType(u"temporary"_ns, Some(PERSISTENCE_TYPE_TEMPORARY));
+ testPersistenceType(u"default"_ns, Some(PERSISTENCE_TYPE_DEFAULT));
+ testPersistenceType(u"persistent"_ns, Nothing());
+ testPersistenceType(u"foobar"_ns, Nothing());
+}
+
+} // namespace mozilla::dom::quota
diff --git a/dom/quota/test/gtest/TestQMResult.cpp b/dom/quota/test/gtest/TestQMResult.cpp
new file mode 100644
index 0000000000..94cbec7364
--- /dev/null
+++ b/dom/quota/test/gtest/TestQMResult.cpp
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Common.h"
+#include "gtest/gtest.h"
+#include "mozilla/dom/QMResult.h"
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+class DOM_Quota_QMResult : public DOM_Quota_Test {};
+
+#ifdef QM_ERROR_STACKS_ENABLED
+TEST_F(DOM_Quota_QMResult, Construct_Default) {
+ QMResult res;
+
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(res.StackId(), ExpectedStackId());
+ ASSERT_EQ(res.FrameId(), 1u);
+ ASSERT_EQ(res.NSResult(), NS_OK);
+}
+#endif
+
+TEST_F(DOM_Quota_QMResult, Construct_FromNSResult) {
+ QMResult res(NS_ERROR_FAILURE);
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(res.StackId(), ExpectedStackId());
+ ASSERT_EQ(res.FrameId(), 1u);
+ ASSERT_EQ(res.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(res, NS_ERROR_FAILURE);
+#endif
+}
+
+#ifdef QM_ERROR_STACKS_ENABLED
+TEST_F(DOM_Quota_QMResult, Propagate) {
+ QMResult res1(NS_ERROR_FAILURE);
+
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(res1.StackId(), ExpectedStackId());
+ ASSERT_EQ(res1.FrameId(), 1u);
+ ASSERT_EQ(res1.NSResult(), NS_ERROR_FAILURE);
+
+ QMResult res2 = res1.Propagate();
+
+ ASSERT_EQ(res2.StackId(), ExpectedStackId());
+ ASSERT_EQ(res2.FrameId(), 2u);
+ ASSERT_EQ(res2.NSResult(), NS_ERROR_FAILURE);
+}
+#endif
+
+TEST_F(DOM_Quota_QMResult, ToQMResult) {
+ auto res = ToQMResult(NS_ERROR_FAILURE);
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(res.StackId(), ExpectedStackId());
+ ASSERT_EQ(res.FrameId(), 1u);
+ ASSERT_EQ(res.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(res, NS_ERROR_FAILURE);
+#endif
+}
diff --git a/dom/quota/test/gtest/TestQuotaCommon.cpp b/dom/quota/test/gtest/TestQuotaCommon.cpp
new file mode 100644
index 0000000000..c1a047a533
--- /dev/null
+++ b/dom/quota/test/gtest/TestQuotaCommon.cpp
@@ -0,0 +1,2171 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/quota/QuotaCommon.h"
+
+#include "gtest/gtest.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <map>
+#include <new>
+#include <ostream>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "ErrorList.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/Unused.h"
+#include "mozilla/fallible.h"
+#include "mozilla/dom/quota/QuotaTestParent.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "nsCOMPtr.h"
+#include "nsLiteralString.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsTLiteralString.h"
+
+class nsISupports;
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+mozilla::ipc::IPCResult QuotaTestParent::RecvTry_Success_CustomErr_QmIpcFail(
+ bool* aTryDidNotReturn) {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_IPC_FAIL(this));
+
+ *aTryDidNotReturn = true;
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult QuotaTestParent::RecvTry_Success_CustomErr_IpcFail(
+ bool* aTryDidNotReturn) {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), IPC_FAIL(this, "Custom why"));
+
+ *aTryDidNotReturn = true;
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+QuotaTestParent::RecvTryInspect_Success_CustomErr_QmIpcFail(
+ bool* aTryDidNotReturn) {
+ QM_TRY_INSPECT(const auto& x, (mozilla::Result<int32_t, nsresult>{42}),
+ QM_IPC_FAIL(this));
+ Unused << x;
+
+ *aTryDidNotReturn = true;
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+QuotaTestParent::RecvTryInspect_Success_CustomErr_IpcFail(
+ bool* aTryDidNotReturn) {
+ QM_TRY_INSPECT(const auto& x, (mozilla::Result<int32_t, nsresult>{42}),
+ IPC_FAIL(this, "Custom why"));
+ Unused << x;
+
+ *aTryDidNotReturn = true;
+
+ return IPC_OK();
+}
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wunreachable-code"
+#endif
+
+TEST(QuotaCommon_Try, Success)
+{
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, Success_CustomErr_QmIpcFail)
+{
+ auto foo = MakeRefPtr<QuotaTestParent>();
+
+ bool tryDidNotReturn = false;
+
+ auto res = foo->RecvTry_Success_CustomErr_QmIpcFail(&tryDidNotReturn);
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_TRUE(res);
+}
+
+TEST(QuotaCommon_Try, Success_CustomErr_IpcFail)
+{
+ auto foo = MakeRefPtr<QuotaTestParent>();
+
+ bool tryDidNotReturn = false;
+
+ auto res = foo->RecvTry_Success_CustomErr_IpcFail(&tryDidNotReturn);
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_TRUE(res);
+}
+
+#ifdef DEBUG
+TEST(QuotaCommon_Try, Success_CustomErr_AssertUnreachable)
+{
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_ASSERT_UNREACHABLE);
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, Success_NoErr_AssertUnreachable)
+{
+ bool tryDidNotReturn = false;
+
+ [&tryDidNotReturn]() -> void {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_ASSERT_UNREACHABLE_VOID);
+
+ tryDidNotReturn = true;
+ }();
+
+ EXPECT_TRUE(tryDidNotReturn);
+}
+#else
+# if defined(QM_ASSERT_UNREACHABLE) || defined(QM_ASSERT_UNREACHABLE_VOID)
+#error QM_ASSERT_UNREACHABLE and QM_ASSERT_UNREACHABLE_VOID should not be defined.
+# endif
+#endif
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+TEST(QuotaCommon_Try, Success_CustomErr_DiagnosticAssertUnreachable)
+{
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_DIAGNOSTIC_ASSERT_UNREACHABLE);
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, Success_NoErr_DiagnosticAssertUnreachable)
+{
+ bool tryDidNotReturn = false;
+
+ [&tryDidNotReturn]() -> void {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID);
+
+ tryDidNotReturn = true;
+ }();
+
+ EXPECT_TRUE(tryDidNotReturn);
+}
+#else
+# if defined(QM_DIAGNOSTIC_ASSERT_UNREACHABLE) || \
+ defined(QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID)
+#error QM_DIAGNOSTIC_ASSERT_UNREACHABLE and QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID should not be defined.
+# endif
+#endif
+
+TEST(QuotaCommon_Try, Success_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryDidNotReturn = false; \
+ \
+ nsresult rv = [&tryDidNotReturn]() -> nsresult { \
+ QM_TRY(MOZ_TO_RESULT(NS_OK), [](__VA_ARGS__) { return aRv; }); \
+ \
+ tryDidNotReturn = true; \
+ \
+ return NS_OK; \
+ }(); \
+ \
+ EXPECT_TRUE(tryDidNotReturn); \
+ EXPECT_EQ(rv, NS_OK); \
+ }
+
+ SUBTEST(const char*, nsresult aRv);
+ SUBTEST(nsresult aRv);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_Try, Success_WithCleanup)
+{
+ bool tryCleanupRan = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryCleanupRan, &tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_PROPAGATE,
+ [&tryCleanupRan](const auto&) { tryCleanupRan = true; });
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryCleanupRan);
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, Failure_PropagateErr)
+{
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Try, Failure_CustomErr)
+{
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), NS_ERROR_UNEXPECTED);
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_Try, Failure_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryDidNotReturn = false; \
+ \
+ nsresult rv = [&tryDidNotReturn]() -> nsresult { \
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), \
+ [](__VA_ARGS__) { return NS_ERROR_UNEXPECTED; }); \
+ \
+ tryDidNotReturn = true; \
+ \
+ return NS_OK; \
+ }(); \
+ \
+ EXPECT_FALSE(tryDidNotReturn); \
+ EXPECT_EQ(rv, NS_ERROR_UNEXPECTED); \
+ }
+
+ SUBTEST(const char* aFunc, nsresult);
+ SUBTEST(nsresult rv);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_Try, Failure_NoErr)
+{
+ bool tryDidNotReturn = false;
+
+ [&tryDidNotReturn]() -> void {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_VOID);
+
+ tryDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryDidNotReturn);
+}
+
+TEST(QuotaCommon_Try, Failure_WithCleanup)
+{
+ bool tryCleanupRan = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryCleanupRan, &tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_PROPAGATE,
+ [&tryCleanupRan](const auto& result) {
+ EXPECT_EQ(result, NS_ERROR_FAILURE);
+
+ tryCleanupRan = true;
+ });
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryCleanupRan);
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Try, Failure_WithCleanup_UnwrapErr)
+{
+ bool tryCleanupRan = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv;
+
+ [&tryCleanupRan, &tryDidNotReturn](nsresult& aRv) -> void {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_VOID,
+ ([&tryCleanupRan, &aRv](auto& result) {
+ EXPECT_EQ(result, NS_ERROR_FAILURE);
+
+ aRv = result;
+
+ tryCleanupRan = true;
+ }));
+
+ tryDidNotReturn = true;
+
+ aRv = NS_OK;
+ }(rv);
+
+ EXPECT_TRUE(tryCleanupRan);
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Try, SameLine)
+{
+ // clang-format off
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_VOID); QM_TRY(MOZ_TO_RESULT(NS_OK), QM_VOID);
+ // clang-format on
+}
+
+TEST(QuotaCommon_Try, NestingMadness_Success)
+{
+ bool nestedTryDidNotReturn = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&nestedTryDidNotReturn, &tryDidNotReturn]() -> nsresult {
+ QM_TRY(([&nestedTryDidNotReturn]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ nestedTryDidNotReturn = true;
+
+ return Ok();
+ }()));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTryDidNotReturn);
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, NestingMadness_Failure)
+{
+ bool nestedTryDidNotReturn = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&nestedTryDidNotReturn, &tryDidNotReturn]() -> nsresult {
+ QM_TRY(([&nestedTryDidNotReturn]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));
+
+ nestedTryDidNotReturn = true;
+
+ return Ok();
+ }()));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(nestedTryDidNotReturn);
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Try, NestingMadness_Multiple_Success)
+{
+ bool nestedTry1DidNotReturn = false;
+ bool nestedTry2DidNotReturn = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&nestedTry1DidNotReturn, &nestedTry2DidNotReturn,
+ &tryDidNotReturn]() -> nsresult {
+ QM_TRY(([&nestedTry1DidNotReturn,
+ &nestedTry2DidNotReturn]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ nestedTry1DidNotReturn = true;
+
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ nestedTry2DidNotReturn = true;
+
+ return Ok();
+ }()));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTry1DidNotReturn);
+ EXPECT_TRUE(nestedTry2DidNotReturn);
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, NestingMadness_Multiple_Failure1)
+{
+ bool nestedTry1DidNotReturn = false;
+ bool nestedTry2DidNotReturn = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&nestedTry1DidNotReturn, &nestedTry2DidNotReturn,
+ &tryDidNotReturn]() -> nsresult {
+ QM_TRY(([&nestedTry1DidNotReturn,
+ &nestedTry2DidNotReturn]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));
+
+ nestedTry1DidNotReturn = true;
+
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ nestedTry2DidNotReturn = true;
+
+ return Ok();
+ }()));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(nestedTry1DidNotReturn);
+ EXPECT_FALSE(nestedTry2DidNotReturn);
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Try, NestingMadness_Multiple_Failure2)
+{
+ bool nestedTry1DidNotReturn = false;
+ bool nestedTry2DidNotReturn = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&nestedTry1DidNotReturn, &nestedTry2DidNotReturn,
+ &tryDidNotReturn]() -> nsresult {
+ QM_TRY(([&nestedTry1DidNotReturn,
+ &nestedTry2DidNotReturn]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ nestedTry1DidNotReturn = true;
+
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));
+
+ nestedTry2DidNotReturn = true;
+
+ return Ok();
+ }()));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTry1DidNotReturn);
+ EXPECT_FALSE(nestedTry2DidNotReturn);
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, Success)
+{
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));
+ EXPECT_EQ(x, 42);
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_TryInspect, Success_CustomErr_QmIpcFail)
+{
+ auto foo = MakeRefPtr<QuotaTestParent>();
+
+ bool tryDidNotReturn = false;
+
+ auto res = foo->RecvTryInspect_Success_CustomErr_QmIpcFail(&tryDidNotReturn);
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_TRUE(res);
+}
+
+TEST(QuotaCommon_TryInspect, Success_CustomErr_IpcFail)
+{
+ auto foo = MakeRefPtr<QuotaTestParent>();
+
+ bool tryDidNotReturn = false;
+
+ auto res = foo->RecvTryInspect_Success_CustomErr_IpcFail(&tryDidNotReturn);
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_TRUE(res);
+}
+
+#ifdef DEBUG
+TEST(QuotaCommon_TryInspect, Success_CustomErr_AssertUnreachable)
+{
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}),
+ QM_ASSERT_UNREACHABLE);
+ EXPECT_EQ(x, 42);
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_TryInspect, Success_NoErr_AssertUnreachable)
+{
+ bool tryInspectDidNotReturn = false;
+
+ [&tryInspectDidNotReturn]() -> void {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}),
+ QM_ASSERT_UNREACHABLE_VOID);
+ EXPECT_EQ(x, 42);
+
+ tryInspectDidNotReturn = true;
+ }();
+
+ EXPECT_TRUE(tryInspectDidNotReturn);
+}
+#endif
+
+TEST(QuotaCommon_TryInspect, Success_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryInspectDidNotReturn = false; \
+ \
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult { \
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}), \
+ [](__VA_ARGS__) { return aRv; }); \
+ EXPECT_EQ(x, 42); \
+ \
+ tryInspectDidNotReturn = true; \
+ \
+ return NS_OK; \
+ }(); \
+ \
+ EXPECT_TRUE(tryInspectDidNotReturn); \
+ EXPECT_EQ(rv, NS_OK); \
+ }
+
+ SUBTEST(const char*, nsresult aRv);
+ SUBTEST(nsresult aRv);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_TryInspect, Success_WithCleanup)
+{
+ bool tryInspectCleanupRan = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectCleanupRan, &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& x, (Result<int32_t, nsresult>{42}), QM_PROPAGATE,
+ [&tryInspectCleanupRan](const auto&) { tryInspectCleanupRan = true; });
+ EXPECT_EQ(x, 42);
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryInspectCleanupRan);
+ EXPECT_TRUE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_TryInspect, Failure_PropagateErr)
+{
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, Failure_CustomErr)
+{
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
+ NS_ERROR_UNEXPECTED);
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_TryInspect, Failure_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryInspectDidNotReturn = false; \
+ \
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult { \
+ QM_TRY_INSPECT(const auto& x, \
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), \
+ [](__VA_ARGS__) { return NS_ERROR_UNEXPECTED; }); \
+ Unused << x; \
+ \
+ tryInspectDidNotReturn = true; \
+ \
+ return NS_OK; \
+ }(); \
+ \
+ EXPECT_FALSE(tryInspectDidNotReturn); \
+ EXPECT_EQ(rv, NS_ERROR_UNEXPECTED); \
+ }
+
+ SUBTEST(const char*, nsresult);
+ SUBTEST(nsresult);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_TryInspect, Failure_NoErr)
+{
+ bool tryInspectDidNotReturn = false;
+
+ [&tryInspectDidNotReturn]() -> void {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), QM_VOID);
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryInspectDidNotReturn);
+}
+
+TEST(QuotaCommon_TryInspect, Failure_WithCleanup)
+{
+ bool tryInspectCleanupRan = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectCleanupRan, &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
+ QM_PROPAGATE, [&tryInspectCleanupRan](const auto& result) {
+ EXPECT_EQ(result, NS_ERROR_FAILURE);
+
+ tryInspectCleanupRan = true;
+ });
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryInspectCleanupRan);
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, Failure_WithCleanup_UnwrapErr)
+{
+ bool tryInspectCleanupRan = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv;
+
+ [&tryInspectCleanupRan, &tryInspectDidNotReturn](nsresult& aRv) -> void {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), QM_VOID,
+ ([&tryInspectCleanupRan, &aRv](auto& result) {
+ EXPECT_EQ(result, NS_ERROR_FAILURE);
+
+ aRv = result;
+
+ tryInspectCleanupRan = true;
+ }));
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+
+ aRv = NS_OK;
+ }(rv);
+
+ EXPECT_TRUE(tryInspectCleanupRan);
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, ConstDecl)
+{
+ QM_TRY_INSPECT(const int32_t& x, (Result<int32_t, nsresult>{42}), QM_VOID);
+
+ static_assert(std::is_same_v<decltype(x), const int32_t&>);
+
+ EXPECT_EQ(x, 42);
+}
+
+TEST(QuotaCommon_TryInspect, SameScopeDecl)
+{
+ QM_TRY_INSPECT(const int32_t& x, (Result<int32_t, nsresult>{42}), QM_VOID);
+ EXPECT_EQ(x, 42);
+
+ QM_TRY_INSPECT(const int32_t& y, (Result<int32_t, nsresult>{42}), QM_VOID);
+ EXPECT_EQ(y, 42);
+}
+
+TEST(QuotaCommon_TryInspect, SameLine)
+{
+ // clang-format off
+ QM_TRY_INSPECT(const auto &x, (Result<int32_t, nsresult>{42}), QM_VOID); QM_TRY_INSPECT(const auto &y, (Result<int32_t, nsresult>{42}), QM_VOID);
+ // clang-format on
+
+ EXPECT_EQ(x, 42);
+ EXPECT_EQ(y, 42);
+}
+
+TEST(QuotaCommon_TryInspect, NestingMadness_Success)
+{
+ bool nestedTryInspectDidNotReturn = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&nestedTryInspectDidNotReturn,
+ &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& x,
+ ([&nestedTryInspectDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));
+
+ nestedTryInspectDidNotReturn = true;
+
+ return x;
+ }()));
+ EXPECT_EQ(x, 42);
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTryInspectDidNotReturn);
+ EXPECT_TRUE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_TryInspect, NestingMadness_Failure)
+{
+ bool nestedTryInspectDidNotReturn = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&nestedTryInspectDidNotReturn,
+ &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& x,
+ ([&nestedTryInspectDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+
+ nestedTryInspectDidNotReturn = true;
+
+ return x;
+ }()));
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(nestedTryInspectDidNotReturn);
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, NestingMadness_Multiple_Success)
+{
+ bool nestedTryInspect1DidNotReturn = false;
+ bool nestedTryInspect2DidNotReturn = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&nestedTryInspect1DidNotReturn, &nestedTryInspect2DidNotReturn,
+ &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& z,
+ ([&nestedTryInspect1DidNotReturn,
+ &nestedTryInspect2DidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));
+
+ nestedTryInspect1DidNotReturn = true;
+
+ QM_TRY_INSPECT(const auto& y, (Result<int32_t, nsresult>{42}));
+
+ nestedTryInspect2DidNotReturn = true;
+
+ return x + y;
+ }()));
+ EXPECT_EQ(z, 84);
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTryInspect1DidNotReturn);
+ EXPECT_TRUE(nestedTryInspect2DidNotReturn);
+ EXPECT_TRUE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_TryInspect, NestingMadness_Multiple_Failure1)
+{
+ bool nestedTryInspect1DidNotReturn = false;
+ bool nestedTryInspect2DidNotReturn = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&nestedTryInspect1DidNotReturn, &nestedTryInspect2DidNotReturn,
+ &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& z,
+ ([&nestedTryInspect1DidNotReturn,
+ &nestedTryInspect2DidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+
+ nestedTryInspect1DidNotReturn = true;
+
+ QM_TRY_INSPECT(const auto& y, (Result<int32_t, nsresult>{42}));
+
+ nestedTryInspect2DidNotReturn = true;
+
+ return x + y;
+ }()));
+ Unused << z;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(nestedTryInspect1DidNotReturn);
+ EXPECT_FALSE(nestedTryInspect2DidNotReturn);
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, NestingMadness_Multiple_Failure2)
+{
+ bool nestedTryInspect1DidNotReturn = false;
+ bool nestedTryInspect2DidNotReturn = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&nestedTryInspect1DidNotReturn, &nestedTryInspect2DidNotReturn,
+ &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& z,
+ ([&nestedTryInspect1DidNotReturn,
+ &nestedTryInspect2DidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));
+
+ nestedTryInspect1DidNotReturn = true;
+
+ QM_TRY_INSPECT(const auto& y,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+
+ nestedTryInspect2DidNotReturn = true;
+
+ return x + y;
+ }()));
+ Unused << z;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTryInspect1DidNotReturn);
+ EXPECT_FALSE(nestedTryInspect2DidNotReturn);
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+// We are not repeating all QM_TRY_INSPECT test cases for QM_TRY_UNWRAP, since
+// they are largely based on the same implementation. We just add some where
+// inspecting and unwrapping differ.
+
+TEST(QuotaCommon_TryUnwrap, NonConstDecl)
+{
+ QM_TRY_UNWRAP(int32_t x, (Result<int32_t, nsresult>{42}), QM_VOID);
+
+ static_assert(std::is_same_v<decltype(x), int32_t>);
+
+ EXPECT_EQ(x, 42);
+}
+
+TEST(QuotaCommon_TryUnwrap, RvalueDecl)
+{
+ QM_TRY_UNWRAP(int32_t && x, (Result<int32_t, nsresult>{42}), QM_VOID);
+
+ static_assert(std::is_same_v<decltype(x), int32_t&&>);
+
+ EXPECT_EQ(x, 42);
+}
+
+TEST(QuotaCommon_TryUnwrap, ParenDecl)
+{
+ QM_TRY_UNWRAP(
+ (auto&& [x, y]),
+ (Result<std::pair<int32_t, bool>, nsresult>{std::pair{42, true}}),
+ QM_VOID);
+
+ static_assert(std::is_same_v<decltype(x), int32_t>);
+ static_assert(std::is_same_v<decltype(y), bool>);
+
+ EXPECT_EQ(x, 42);
+ EXPECT_EQ(y, true);
+}
+
+TEST(QuotaCommon_TryReturn, Success)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn] {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{42}));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_TryReturn, Success_nsresult)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn] {
+ QM_TRY_RETURN(MOZ_TO_RESULT(NS_OK));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isOk());
+}
+
+#ifdef DEBUG
+TEST(QuotaCommon_TryReturn, Success_CustomErr_AssertUnreachable)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{42}), QM_ASSERT_UNREACHABLE);
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+#endif
+
+TEST(QuotaCommon_TryReturn, Success_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryReturnDidNotReturn = false; \
+ \
+ auto res = [&tryReturnDidNotReturn]() -> Result<Ok, nsresult> { \
+ QM_TRY_RETURN(MOZ_TO_RESULT(NS_OK), \
+ [](__VA_ARGS__) { return Err(aRv); }); \
+ \
+ tryReturnDidNotReturn = true; \
+ }(); \
+ \
+ EXPECT_FALSE(tryReturnDidNotReturn); \
+ EXPECT_TRUE(res.isOk()); \
+ }
+
+ SUBTEST(const char*, nsresult aRv);
+ SUBTEST(nsresult aRv);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_TryReturn, Success_WithCleanup)
+{
+ bool tryReturnCleanupRan = false;
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnCleanupRan,
+ &tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_RETURN(
+ (Result<int32_t, nsresult>{42}), QM_PROPAGATE,
+ [&tryReturnCleanupRan](const auto&) { tryReturnCleanupRan = true; });
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnCleanupRan);
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_TryReturn, Failure_PropagateErr)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn] {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryReturn, Failure_PropagateErr_nsresult)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn] {
+ QM_TRY_RETURN(MOZ_TO_RESULT(NS_ERROR_FAILURE));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryReturn, Failure_CustomErr)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
+ Err(NS_ERROR_UNEXPECTED));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_TryReturn, Failure_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryReturnDidNotReturn = false; \
+ \
+ auto res = [&tryReturnDidNotReturn]() -> Result<int32_t, nsresult> { \
+ QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), \
+ [](__VA_ARGS__) { return Err(NS_ERROR_UNEXPECTED); }); \
+ \
+ tryReturnDidNotReturn = true; \
+ }(); \
+ \
+ EXPECT_FALSE(tryReturnDidNotReturn); \
+ EXPECT_TRUE(res.isErr()); \
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED); \
+ }
+
+ SUBTEST(const char*, nsresult);
+ SUBTEST(nsresult);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_TryReturn, Failure_WithCleanup)
+{
+ bool tryReturnCleanupRan = false;
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnCleanupRan,
+ &tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
+ QM_PROPAGATE, [&tryReturnCleanupRan](const auto& result) {
+ EXPECT_EQ(result, NS_ERROR_FAILURE);
+
+ tryReturnCleanupRan = true;
+ });
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_TRUE(tryReturnCleanupRan);
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryReturn, SameLine)
+{
+ // clang-format off
+ auto res1 = [] { QM_TRY_RETURN((Result<int32_t, nsresult>{42})); }(); auto res2 = []() -> Result<int32_t, nsresult> { QM_TRY_RETURN((Result<int32_t, nsresult>{42})); }();
+ // clang-format on
+
+ EXPECT_TRUE(res1.isOk());
+ EXPECT_EQ(res1.unwrap(), 42);
+ EXPECT_TRUE(res2.isOk());
+ EXPECT_EQ(res2.unwrap(), 42);
+}
+
+TEST(QuotaCommon_TryReturn, NestingMadness_Success)
+{
+ bool nestedTryReturnDidNotReturn = false;
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&nestedTryReturnDidNotReturn, &tryReturnDidNotReturn] {
+ QM_TRY_RETURN(([&nestedTryReturnDidNotReturn] {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{42}));
+
+ nestedTryReturnDidNotReturn = true;
+ }()));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(nestedTryReturnDidNotReturn);
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_TryReturn, NestingMadness_Failure)
+{
+ bool nestedTryReturnDidNotReturn = false;
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&nestedTryReturnDidNotReturn, &tryReturnDidNotReturn] {
+ QM_TRY_RETURN(([&nestedTryReturnDidNotReturn] {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+
+ nestedTryReturnDidNotReturn = true;
+ }()));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(nestedTryReturnDidNotReturn);
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Fail, ReturnValue)
+{
+ bool failDidNotReturn = false;
+
+ nsresult rv = [&failDidNotReturn]() -> nsresult {
+ QM_FAIL(NS_ERROR_FAILURE);
+
+ failDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(failDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Fail, ReturnValue_WithCleanup)
+{
+ bool failCleanupRan = false;
+ bool failDidNotReturn = false;
+
+ nsresult rv = [&failCleanupRan, &failDidNotReturn]() -> nsresult {
+ QM_FAIL(NS_ERROR_FAILURE, [&failCleanupRan]() { failCleanupRan = true; });
+
+ failDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(failCleanupRan);
+ EXPECT_FALSE(failDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_WarnOnlyTry, Success)
+{
+ bool warnOnlyTryDidNotReturn = false;
+
+ const auto res =
+ [&warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY(OkIf(true));
+
+ warnOnlyTryDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTry, Success_WithCleanup)
+{
+ bool warnOnlyTryCleanupRan = false;
+ bool warnOnlyTryDidNotReturn = false;
+
+ const auto res =
+ [&warnOnlyTryCleanupRan,
+ &warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY(OkIf(true), [&warnOnlyTryCleanupRan](const auto&) {
+ warnOnlyTryCleanupRan = true;
+ });
+
+ warnOnlyTryDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_FALSE(warnOnlyTryCleanupRan);
+ EXPECT_TRUE(warnOnlyTryDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTry, Failure)
+{
+ bool warnOnlyTryDidNotReturn = false;
+
+ const auto res =
+ [&warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY(OkIf(false));
+
+ warnOnlyTryDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTry, Failure_WithCleanup)
+{
+ bool warnOnlyTryCleanupRan = false;
+ bool warnOnlyTryDidNotReturn = false;
+
+ const auto res =
+ [&warnOnlyTryCleanupRan,
+ &warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY(OkIf(false), ([&warnOnlyTryCleanupRan](const auto&) {
+ warnOnlyTryCleanupRan = true;
+ }));
+
+ warnOnlyTryDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryCleanupRan);
+ EXPECT_TRUE(warnOnlyTryDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTryUnwrap, Success)
+{
+ bool warnOnlyTryUnwrapDidNotReturn = false;
+
+ const auto res = [&warnOnlyTryUnwrapDidNotReturn]()
+ -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY_UNWRAP(const auto x, (Result<int32_t, NotOk>{42}));
+ EXPECT_TRUE(x);
+ EXPECT_EQ(*x, 42);
+
+ warnOnlyTryUnwrapDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTryUnwrap, Success_WithCleanup)
+{
+ bool warnOnlyTryUnwrapCleanupRan = false;
+ bool warnOnlyTryUnwrapDidNotReturn = false;
+
+ const auto res = [&warnOnlyTryUnwrapCleanupRan,
+ &warnOnlyTryUnwrapDidNotReturn]()
+ -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY_UNWRAP(const auto x, (Result<int32_t, NotOk>{42}),
+ [&warnOnlyTryUnwrapCleanupRan](const auto&) {
+ warnOnlyTryUnwrapCleanupRan = true;
+ });
+ EXPECT_TRUE(x);
+ EXPECT_EQ(*x, 42);
+
+ warnOnlyTryUnwrapDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_FALSE(warnOnlyTryUnwrapCleanupRan);
+ EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTryUnwrap, Failure)
+{
+ bool warnOnlyTryUnwrapDidNotReturn = false;
+
+ const auto res = [&warnOnlyTryUnwrapDidNotReturn]()
+ -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY_UNWRAP(const auto x,
+ (Result<int32_t, NotOk>{Err(NotOk{})}));
+ EXPECT_FALSE(x);
+
+ warnOnlyTryUnwrapDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTryUnwrap, Failure_WithCleanup)
+{
+ bool warnOnlyTryUnwrapCleanupRan = false;
+ bool warnOnlyTryUnwrapDidNotReturn = false;
+
+ const auto res = [&warnOnlyTryUnwrapCleanupRan,
+ &warnOnlyTryUnwrapDidNotReturn]()
+ -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY_UNWRAP(const auto x, (Result<int32_t, NotOk>{Err(NotOk{})}),
+ [&warnOnlyTryUnwrapCleanupRan](const auto&) {
+ warnOnlyTryUnwrapCleanupRan = true;
+ });
+ EXPECT_FALSE(x);
+
+ warnOnlyTryUnwrapDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryUnwrapCleanupRan);
+ EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
+}
+
+TEST(QuotaCommon_OrElseWarn, Success)
+{
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN(OkIf(true), ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{
+ mozilla::Ok{}};
+ })));
+
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_FALSE(fallbackRun);
+ EXPECT_TRUE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarn, Failure_MappedToSuccess)
+{
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ // XXX Consider allowing to set a custom error handler, so that we can
+ // actually assert that a warning was emitted.
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN(OkIf(false), ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{
+ mozilla::Ok{}};
+ })));
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(fallbackRun);
+ EXPECT_TRUE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarn, Failure_MappedToError)
+{
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ // XXX Consider allowing to set a custom error handler, so that we can
+ // actually assert that a warning was emitted.
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN(OkIf(false), ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{
+ NotOk{}};
+ })));
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_TRUE(fallbackRun);
+ EXPECT_FALSE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarnIf, Success)
+{
+ bool predicateRun = false;
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ OkIf(true),
+ [&predicateRun](const NotOk) {
+ predicateRun = true;
+ return false;
+ },
+ ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{mozilla::Ok{}};
+ })));
+
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_FALSE(predicateRun);
+ EXPECT_FALSE(fallbackRun);
+ EXPECT_TRUE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarnIf, Failure_PredicateReturnsFalse)
+{
+ bool predicateRun = false;
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ OkIf(false),
+ [&predicateRun](const NotOk) {
+ predicateRun = true;
+ return false;
+ },
+ ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{mozilla::Ok{}};
+ })));
+
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_TRUE(predicateRun);
+ EXPECT_FALSE(fallbackRun);
+ EXPECT_FALSE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarnIf, Failure_PredicateReturnsTrue_MappedToSuccess)
+{
+ bool predicateRun = false;
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ OkIf(false),
+ [&predicateRun](const NotOk) {
+ predicateRun = true;
+ return true;
+ },
+ ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{mozilla::Ok{}};
+ })));
+
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(predicateRun);
+ EXPECT_TRUE(fallbackRun);
+ EXPECT_TRUE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarnIf, Failure_PredicateReturnsTrue_MappedToError)
+{
+ bool predicateRun = false;
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ OkIf(false),
+ [&predicateRun](const NotOk) {
+ predicateRun = true;
+ return true;
+ },
+ ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{mozilla::NotOk{}};
+ })));
+
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_TRUE(predicateRun);
+ EXPECT_TRUE(fallbackRun);
+ EXPECT_FALSE(tryContinued);
+}
+
+TEST(QuotaCommon_OkIf, True)
+{
+ auto res = OkIf(true);
+
+ EXPECT_TRUE(res.isOk());
+}
+
+TEST(QuotaCommon_OkIf, False)
+{
+ auto res = OkIf(false);
+
+ EXPECT_TRUE(res.isErr());
+}
+
+TEST(QuotaCommon_OkToOk, Bool_True)
+{
+ auto res = OkToOk<true>(Ok());
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), true);
+}
+
+TEST(QuotaCommon_OkToOk, Bool_False)
+{
+ auto res = OkToOk<false>(Ok());
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), false);
+}
+
+TEST(QuotaCommon_OkToOk, Int_42)
+{
+ auto res = OkToOk<42>(Ok());
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Bool_True)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, true>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), true);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Bool_True_Err)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, true>(NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Bool_False)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, false>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), false);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Bool_False_Err)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, false>(NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Int_42)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, 42>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Int_42_Err)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, 42>(NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, NsCOMPtr_nullptr)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, nullptr, nsCOMPtr<nsISupports>>(
+ NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), nullptr);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, NsCOMPtr_nullptr_Err)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, nullptr, nsCOMPtr<nsISupports>>(
+ NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_ErrToDefaultOkOrErr, Ok)
+{
+ auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, Ok>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+}
+
+TEST(QuotaCommon_ErrToDefaultOkOrErr, Ok_Err)
+{
+ auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, Ok>(NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_ErrToDefaultOkOrErr, NsCOMPtr)
+{
+ auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, nsCOMPtr<nsISupports>>(
+ NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), nullptr);
+}
+
+TEST(QuotaCommon_ErrToDefaultOkOrErr, NsCOMPtr_Err)
+{
+ auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, nsCOMPtr<nsISupports>>(
+ NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_IsSpecificError, Match)
+{ EXPECT_TRUE(IsSpecificError<NS_ERROR_FAILURE>(NS_ERROR_FAILURE)); }
+
+TEST(QuotaCommon_IsSpecificError, Mismatch)
+{ EXPECT_FALSE(IsSpecificError<NS_ERROR_FAILURE>(NS_ERROR_UNEXPECTED)); }
+
+TEST(QuotaCommon_ErrToOk, Bool_True)
+{
+ auto res = ErrToOk<true>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), true);
+}
+
+TEST(QuotaCommon_ErrToOk, Bool_False)
+{
+ auto res = ErrToOk<false>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), false);
+}
+
+TEST(QuotaCommon_ErrToOk, Int_42)
+{
+ auto res = ErrToOk<42>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_ErrToOk, NsCOMPtr_nullptr)
+{
+ auto res = ErrToOk<nullptr, nsCOMPtr<nsISupports>>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), nullptr);
+}
+
+TEST(QuotaCommon_ErrToDefaultOk, Ok)
+{
+ auto res = ErrToDefaultOk<Ok>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+}
+
+TEST(QuotaCommon_ErrToDefaultOk, NsCOMPtr)
+{
+ auto res = ErrToDefaultOk<nsCOMPtr<nsISupports>>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), nullptr);
+}
+
+class StringPairParameterized
+ : public ::testing::TestWithParam<std::pair<const char*, const char*>> {};
+
+TEST_P(StringPairParameterized, AnonymizedOriginString) {
+ const auto [in, expectedAnonymized] = GetParam();
+ const auto anonymized = AnonymizedOriginString(nsDependentCString(in));
+ EXPECT_STREQ(anonymized.get(), expectedAnonymized);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ QuotaCommon, StringPairParameterized,
+ ::testing::Values(
+ // XXX Do we really want to anonymize about: origins?
+ std::pair("about:home", "about:aaaa"),
+ std::pair("https://foo.bar.com", "https://aaa.aaa.aaa"),
+ std::pair("https://foo.bar.com:8000", "https://aaa.aaa.aaa:DDDD"),
+ std::pair("file://UNIVERSAL_FILE_ORIGIN",
+ "file://aaaaaaaaa_aaaa_aaaaaa")));
+
+// BEGIN COPY FROM mfbt/tests/TestResult.cpp
+struct Failed {};
+
+static GenericErrorResult<Failed> Fail() { return Err(Failed()); }
+
+static Result<Ok, Failed> Task1(bool pass) {
+ if (!pass) {
+ return Fail(); // implicit conversion from GenericErrorResult to Result
+ }
+ return Ok();
+}
+// END COPY FROM mfbt/tests/TestResult.cpp
+
+static Result<bool, Failed> Condition(bool aNoError, bool aResult) {
+ return Task1(aNoError).map([aResult](auto) { return aResult; });
+}
+
+TEST(QuotaCommon_CollectWhileTest, NoFailures)
+{
+ const size_t loopCount = 5;
+ size_t conditionExecutions = 0;
+ size_t bodyExecutions = 0;
+ auto result = CollectWhile(
+ [&conditionExecutions] {
+ ++conditionExecutions;
+ return Condition(true, conditionExecutions <= loopCount);
+ },
+ [&bodyExecutions] {
+ ++bodyExecutions;
+ return Task1(true);
+ });
+ static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
+ MOZ_RELEASE_ASSERT(result.isOk());
+ MOZ_RELEASE_ASSERT(loopCount == bodyExecutions);
+ MOZ_RELEASE_ASSERT(1 + loopCount == conditionExecutions);
+}
+
+TEST(QuotaCommon_CollectWhileTest, BodyFailsImmediately)
+{
+ size_t conditionExecutions = 0;
+ size_t bodyExecutions = 0;
+ auto result = CollectWhile(
+ [&conditionExecutions] {
+ ++conditionExecutions;
+ return Condition(true, true);
+ },
+ [&bodyExecutions] {
+ ++bodyExecutions;
+ return Task1(false);
+ });
+ static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(1 == bodyExecutions);
+ MOZ_RELEASE_ASSERT(1 == conditionExecutions);
+}
+
+TEST(QuotaCommon_CollectWhileTest, BodyFailsOnSecondExecution)
+{
+ size_t conditionExecutions = 0;
+ size_t bodyExecutions = 0;
+ auto result = CollectWhile(
+ [&conditionExecutions] {
+ ++conditionExecutions;
+ return Condition(true, true);
+ },
+ [&bodyExecutions] {
+ ++bodyExecutions;
+ return Task1(bodyExecutions < 2);
+ });
+ static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(2 == bodyExecutions);
+ MOZ_RELEASE_ASSERT(2 == conditionExecutions);
+}
+
+TEST(QuotaCommon_CollectWhileTest, ConditionFailsImmediately)
+{
+ size_t conditionExecutions = 0;
+ size_t bodyExecutions = 0;
+ auto result = CollectWhile(
+ [&conditionExecutions] {
+ ++conditionExecutions;
+ return Condition(false, true);
+ },
+ [&bodyExecutions] {
+ ++bodyExecutions;
+ return Task1(true);
+ });
+ static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(0 == bodyExecutions);
+ MOZ_RELEASE_ASSERT(1 == conditionExecutions);
+}
+
+TEST(QuotaCommon_CollectWhileTest, ConditionFailsOnSecondExecution)
+{
+ size_t conditionExecutions = 0;
+ size_t bodyExecutions = 0;
+ auto result = CollectWhile(
+ [&conditionExecutions] {
+ ++conditionExecutions;
+ return Condition(conditionExecutions < 2, true);
+ },
+ [&bodyExecutions] {
+ ++bodyExecutions;
+ return Task1(true);
+ });
+ static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(1 == bodyExecutions);
+ MOZ_RELEASE_ASSERT(2 == conditionExecutions);
+}
+
+TEST(QuotaCommon_CollectEachInRange, Success)
+{
+ size_t bodyExecutions = 0;
+ const auto result = CollectEachInRange(
+ std::array<int, 5>{{1, 2, 3, 4, 5}},
+ [&bodyExecutions](const int val) -> Result<Ok, nsresult> {
+ ++bodyExecutions;
+ return Ok{};
+ });
+
+ MOZ_RELEASE_ASSERT(result.isOk());
+ MOZ_RELEASE_ASSERT(5 == bodyExecutions);
+}
+
+TEST(QuotaCommon_CollectEachInRange, FailureShortCircuit)
+{
+ size_t bodyExecutions = 0;
+ const auto result = CollectEachInRange(
+ std::array<int, 5>{{1, 2, 3, 4, 5}},
+ [&bodyExecutions](const int val) -> Result<Ok, nsresult> {
+ ++bodyExecutions;
+ return val == 3 ? Err(NS_ERROR_FAILURE) : Result<Ok, nsresult>{Ok{}};
+ });
+
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(NS_ERROR_FAILURE == result.inspectErr());
+ MOZ_RELEASE_ASSERT(3 == bodyExecutions);
+}
+
+TEST(QuotaCommon_ReduceEach, Success)
+{
+ const auto result = ReduceEach(
+ [i = int{0}]() mutable -> Result<int, Failed> {
+ if (i < 5) {
+ return ++i;
+ }
+ return 0;
+ },
+ 0, [](int val, int add) -> Result<int, Failed> { return val + add; });
+ static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);
+
+ MOZ_RELEASE_ASSERT(result.isOk());
+ MOZ_RELEASE_ASSERT(15 == result.inspect());
+}
+
+TEST(QuotaCommon_ReduceEach, StepError)
+{
+ const auto result = ReduceEach(
+ [i = int{0}]() mutable -> Result<int, Failed> {
+ if (i < 5) {
+ return ++i;
+ }
+ return 0;
+ },
+ 0,
+ [](int val, int add) -> Result<int, Failed> {
+ if (val > 2) {
+ return Err(Failed{});
+ }
+ return val + add;
+ });
+ static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);
+
+ MOZ_RELEASE_ASSERT(result.isErr());
+}
+
+TEST(QuotaCommon_ReduceEach, GeneratorError)
+{
+ size_t generatorExecutions = 0;
+ const auto result = ReduceEach(
+ [i = int{0}, &generatorExecutions]() mutable -> Result<int, Failed> {
+ ++generatorExecutions;
+ if (i < 1) {
+ return ++i;
+ }
+ return Err(Failed{});
+ },
+ 0,
+ [](int val, int add) -> Result<int, Failed> {
+ if (val > 2) {
+ return Err(Failed{});
+ }
+ return val + add;
+ });
+ static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);
+
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(2 == generatorExecutions);
+}
+
+TEST(QuotaCommon_Reduce, Success)
+{
+ const auto range = std::vector{0, 1, 2, 3, 4, 5};
+ const auto result = Reduce(
+ range, 0, [](int val, Maybe<const int&> add) -> Result<int, Failed> {
+ return val + add.ref();
+ });
+ static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);
+
+ MOZ_RELEASE_ASSERT(result.isOk());
+ MOZ_RELEASE_ASSERT(15 == result.inspect());
+}
+
+TEST(QuotaCommon_CallWithDelayedRetriesIfAccessDenied, NoFailures)
+{
+ uint32_t tries = 0;
+
+ auto res = CallWithDelayedRetriesIfAccessDenied(
+ [&tries]() -> Result<Ok, nsresult> {
+ ++tries;
+ return Ok{};
+ },
+ 10, 2);
+
+ EXPECT_EQ(tries, 1u);
+ EXPECT_TRUE(res.isOk());
+}
+
+TEST(QuotaCommon_CallWithDelayedRetriesIfAccessDenied, PermanentFailures)
+{
+ uint32_t tries = 0;
+
+ auto res = CallWithDelayedRetriesIfAccessDenied(
+ [&tries]() -> Result<Ok, nsresult> {
+ ++tries;
+ return Err(NS_ERROR_FILE_IS_LOCKED);
+ },
+ 10, 2);
+
+ EXPECT_EQ(tries, 11u);
+ EXPECT_TRUE(res.isErr());
+}
+
+TEST(QuotaCommon_CallWithDelayedRetriesIfAccessDenied, FailuresAndSuccess)
+{
+ uint32_t tries = 0;
+
+ auto res = CallWithDelayedRetriesIfAccessDenied(
+ [&tries]() -> Result<Ok, nsresult> {
+ if (++tries == 5) {
+ return Ok{};
+ }
+ return Err(NS_ERROR_FILE_ACCESS_DENIED);
+ },
+ 10, 2);
+
+ EXPECT_EQ(tries, 5u);
+ EXPECT_TRUE(res.isOk());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, ThisSourceFile)
+{
+ static constexpr auto thisSourceFileRelativePath =
+ "dom/quota/test/gtest/TestQuotaCommon.cpp"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ nsLiteralCString(__FILE__))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(), thisSourceFileRelativePath.get());
+}
+
+static nsCString MakeTreePath(const nsACString& aBasePath,
+ const nsACString& aRelativePath) {
+ nsCString path{aBasePath};
+
+ path.Append("/");
+ path.Append(aRelativePath);
+
+ return path;
+}
+
+static nsCString MakeSourceTreePath(const nsACString& aRelativePath) {
+ return MakeTreePath(mozilla::dom::quota::detail::GetSourceTreeBase(),
+ aRelativePath);
+}
+
+static nsCString MakeObjdirDistIncludeTreePath(
+ const nsACString& aRelativePath) {
+ return MakeTreePath(
+ mozilla::dom::quota::detail::GetObjdirDistIncludeTreeBase(),
+ aRelativePath);
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, DomQuotaSourceFile)
+{
+ static constexpr auto domQuotaSourceFileRelativePath =
+ "dom/quota/ActorsParent.cpp"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ MakeSourceTreePath(domQuotaSourceFileRelativePath))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(),
+ domQuotaSourceFileRelativePath.get());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, DomQuotaSourceFile_Exported)
+{
+ static constexpr auto mozillaDomQuotaSourceFileRelativePath =
+ "mozilla/dom/quota/QuotaCommon.h"_ns;
+
+ static constexpr auto domQuotaSourceFileRelativePath =
+ "dom/quota/QuotaCommon.h"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ MakeObjdirDistIncludeTreePath(
+ mozillaDomQuotaSourceFileRelativePath))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(),
+ domQuotaSourceFileRelativePath.get());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, DomIndexedDBSourceFile)
+{
+ static constexpr auto domIndexedDBSourceFileRelativePath =
+ "dom/indexedDB/ActorsParent.cpp"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ MakeSourceTreePath(domIndexedDBSourceFileRelativePath))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(),
+ domIndexedDBSourceFileRelativePath.get());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath,
+ DomLocalstorageSourceFile_Exported_Mapped)
+{
+ static constexpr auto mozillaDomSourceFileRelativePath =
+ "mozilla/dom/LocalStorageCommon.h"_ns;
+
+ static constexpr auto domLocalstorageSourceFileRelativePath =
+ "dom/localstorage/LocalStorageCommon.h"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ MakeObjdirDistIncludeTreePath(mozillaDomSourceFileRelativePath))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(),
+ domLocalstorageSourceFileRelativePath.get());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, NonDomSourceFile)
+{
+ static constexpr auto nonDomSourceFileRelativePath =
+ "storage/mozStorageService.cpp"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ MakeSourceTreePath(nonDomSourceFileRelativePath))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(),
+ nonDomSourceFileRelativePath.get());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, OtherSourceFile)
+{
+ constexpr auto otherSourceFilePath = "/foo/bar/Test.cpp"_ns;
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ otherSourceFilePath)};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(), "Test.cpp");
+}
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
diff --git a/dom/quota/test/gtest/TestQuotaManager.cpp b/dom/quota/test/gtest/TestQuotaManager.cpp
new file mode 100644
index 0000000000..7d185df481
--- /dev/null
+++ b/dom/quota/test/gtest/TestQuotaManager.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/quota/OriginScope.h"
+
+#include "gtest/gtest.h"
+
+#include <cstdint>
+#include <memory>
+#include "ErrorList.h"
+#include "mozilla/Result.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/fallible.h"
+#include "nsCOMPtr.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsLiteralString.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsTLiteralString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+namespace {
+
+struct OriginTest {
+ const char* mOrigin;
+ bool mMatch;
+};
+
+void CheckOriginScopeMatchesOrigin(const OriginScope& aOriginScope,
+ const char* aOrigin, bool aMatch) {
+ bool result = aOriginScope.Matches(
+ OriginScope::FromOrigin(nsDependentCString(aOrigin)));
+
+ EXPECT_TRUE(result == aMatch);
+}
+
+void CheckUnknownFileEntry(nsIFile& aBase, const nsAString& aName,
+ const bool aWarnIfFile, const bool aWarnIfDir) {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aBase.Clone(getter_AddRefs(file));
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = file->Append(aName);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
+ ASSERT_EQ(rv, NS_OK);
+
+ auto okOrErr = WARN_IF_FILE_IS_UNKNOWN(*file);
+ ASSERT_TRUE(okOrErr.isOk());
+
+#ifdef DEBUG
+ EXPECT_TRUE(okOrErr.inspect() == aWarnIfFile);
+#else
+ EXPECT_TRUE(okOrErr.inspect() == false);
+#endif
+
+ rv = file->Remove(false);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ ASSERT_EQ(rv, NS_OK);
+
+ okOrErr = WARN_IF_FILE_IS_UNKNOWN(*file);
+ ASSERT_TRUE(okOrErr.isOk());
+
+#ifdef DEBUG
+ EXPECT_TRUE(okOrErr.inspect() == aWarnIfDir);
+#else
+ EXPECT_TRUE(okOrErr.inspect() == false);
+#endif
+
+ rv = file->Remove(false);
+ ASSERT_EQ(rv, NS_OK);
+}
+
+} // namespace
+
+TEST(QuotaManager, OriginScope)
+{
+ OriginScope originScope;
+
+ // Sanity checks.
+
+ {
+ constexpr auto origin = "http://www.mozilla.org"_ns;
+ originScope.SetFromOrigin(origin);
+ EXPECT_TRUE(originScope.IsOrigin());
+ EXPECT_TRUE(originScope.GetOrigin().Equals(origin));
+ EXPECT_TRUE(originScope.GetOriginNoSuffix().Equals(origin));
+ }
+
+ {
+ constexpr auto prefix = "http://www.mozilla.org"_ns;
+ originScope.SetFromPrefix(prefix);
+ EXPECT_TRUE(originScope.IsPrefix());
+ EXPECT_TRUE(originScope.GetOriginNoSuffix().Equals(prefix));
+ }
+
+ {
+ originScope.SetFromNull();
+ EXPECT_TRUE(originScope.IsNull());
+ }
+
+ // Test each origin scope type against particular origins.
+
+ {
+ originScope.SetFromOrigin("http://www.mozilla.org"_ns);
+
+ static const OriginTest tests[] = {
+ {"http://www.mozilla.org", true},
+ {"http://www.example.org", false},
+ };
+
+ for (const auto& test : tests) {
+ CheckOriginScopeMatchesOrigin(originScope, test.mOrigin, test.mMatch);
+ }
+ }
+
+ {
+ originScope.SetFromPrefix("http://www.mozilla.org"_ns);
+
+ static const OriginTest tests[] = {
+ {"http://www.mozilla.org", true},
+ {"http://www.mozilla.org^userContextId=1", true},
+ {"http://www.example.org^userContextId=1", false},
+ };
+
+ for (const auto& test : tests) {
+ CheckOriginScopeMatchesOrigin(originScope, test.mOrigin, test.mMatch);
+ }
+ }
+
+ {
+ originScope.SetFromNull();
+
+ static const OriginTest tests[] = {
+ {"http://www.mozilla.org", true},
+ {"http://www.mozilla.org^userContextId=1", true},
+ {"http://www.example.org^userContextId=1", true},
+ };
+
+ for (const auto& test : tests) {
+ CheckOriginScopeMatchesOrigin(originScope, test.mOrigin, test.mMatch);
+ }
+ }
+}
+
+TEST(QuotaManager, WarnIfUnknownFile)
+{
+ nsCOMPtr<nsIFile> base;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base));
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = base->Append(u"mozquotatests"_ns);
+ ASSERT_EQ(rv, NS_OK);
+
+ base->Remove(true);
+
+ rv = base->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ ASSERT_EQ(rv, NS_OK);
+
+ CheckUnknownFileEntry(*base, u"foo.bar"_ns, true, true);
+ CheckUnknownFileEntry(*base, u".DS_Store"_ns, false, true);
+ CheckUnknownFileEntry(*base, u".desktop"_ns, false, true);
+ CheckUnknownFileEntry(*base, u"desktop.ini"_ns, false, true);
+ CheckUnknownFileEntry(*base, u"DESKTOP.INI"_ns, false, true);
+ CheckUnknownFileEntry(*base, u"thumbs.db"_ns, false, true);
+ CheckUnknownFileEntry(*base, u"THUMBS.DB"_ns, false, true);
+ CheckUnknownFileEntry(*base, u".xyz"_ns, false, true);
+
+ rv = base->Remove(true);
+ ASSERT_EQ(rv, NS_OK);
+}
diff --git a/dom/quota/test/gtest/TestResultExtensions.cpp b/dom/quota/test/gtest/TestResultExtensions.cpp
new file mode 100644
index 0000000000..137acc1423
--- /dev/null
+++ b/dom/quota/test/gtest/TestResultExtensions.cpp
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Common.h"
+#include "gtest/gtest.h"
+#include "mozilla/dom/QMResult.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+namespace {
+class TestClass {
+ public:
+ static constexpr int kTestValue = 42;
+
+ nsresult NonOverloadedNoInputComplex(std::pair<int, int>* aOut) {
+ *aOut = std::pair{kTestValue, kTestValue};
+ return NS_OK;
+ }
+ nsresult NonOverloadedNoInputFailsComplex(std::pair<int, int>* aOut) {
+ return NS_ERROR_FAILURE;
+ }
+};
+} // namespace
+
+class DOM_Quota_ResultExtensions_ToResult : public DOM_Quota_Test {};
+class DOM_Quota_ResultExtensions_GenericErrorResult : public DOM_Quota_Test {};
+
+TEST_F(DOM_Quota_ResultExtensions_ToResult, FromBool) {
+ // success
+ {
+ auto res = ToResult(true);
+ static_assert(std::is_same_v<decltype(res), Result<Ok, nsresult>>);
+ EXPECT_TRUE(res.isOk());
+ }
+
+ // failure
+ {
+ auto res = ToResult(false);
+ static_assert(std::is_same_v<decltype(res), Result<Ok, nsresult>>);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+ }
+}
+
+TEST_F(DOM_Quota_ResultExtensions_ToResult, FromQMResult_Failure) {
+ // copy
+ {
+ const auto res = ToQMResult(NS_ERROR_FAILURE);
+ auto okOrErr = ToResult<QMResult>(res);
+ static_assert(std::is_same_v<decltype(okOrErr), OkOrErr>);
+ ASSERT_TRUE(okOrErr.isErr());
+ auto err = okOrErr.unwrapErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(err.StackId(), ExpectedStackId());
+ ASSERT_EQ(err.FrameId(), 1u);
+ ASSERT_EQ(err.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err, NS_ERROR_FAILURE);
+#endif
+ }
+
+ // move
+ {
+ auto res = ToQMResult(NS_ERROR_FAILURE);
+ auto okOrErr = ToResult<QMResult>(std::move(res));
+ static_assert(std::is_same_v<decltype(okOrErr), OkOrErr>);
+ ASSERT_TRUE(okOrErr.isErr());
+ auto err = okOrErr.unwrapErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(err.StackId(), ExpectedStackId());
+ ASSERT_EQ(err.FrameId(), 1u);
+ ASSERT_EQ(err.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err, NS_ERROR_FAILURE);
+#endif
+ }
+}
+
+TEST_F(DOM_Quota_ResultExtensions_ToResult, FromNSResult_Failure_Macro) {
+ auto okOrErr = QM_TO_RESULT(NS_ERROR_FAILURE);
+ static_assert(std::is_same_v<decltype(okOrErr), OkOrErr>);
+ ASSERT_TRUE(okOrErr.isErr());
+ auto err = okOrErr.unwrapErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(err.StackId(), ExpectedStackId());
+ ASSERT_EQ(err.FrameId(), 1u);
+ ASSERT_EQ(err.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err, NS_ERROR_FAILURE);
+#endif
+}
+
+TEST_F(DOM_Quota_ResultExtensions_GenericErrorResult, ErrorPropagation) {
+ OkOrErr okOrErr1 = ToResult<QMResult>(ToQMResult(NS_ERROR_FAILURE));
+ const auto& err1 = okOrErr1.inspectErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(err1.StackId(), ExpectedStackId());
+ ASSERT_EQ(err1.FrameId(), 1u);
+ ASSERT_EQ(err1.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err1, NS_ERROR_FAILURE);
+#endif
+
+ OkOrErr okOrErr2 = okOrErr1.propagateErr();
+ const auto& err2 = okOrErr2.inspectErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ ASSERT_EQ(err2.StackId(), ExpectedStackId());
+ ASSERT_EQ(err2.FrameId(), 2u);
+ ASSERT_EQ(err2.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err2, NS_ERROR_FAILURE);
+#endif
+
+ OkOrErr okOrErr3 = okOrErr2.propagateErr();
+ const auto& err3 = okOrErr3.inspectErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ ASSERT_EQ(err3.StackId(), ExpectedStackId());
+ ASSERT_EQ(err3.FrameId(), 3u);
+ ASSERT_EQ(err3.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err3, NS_ERROR_FAILURE);
+#endif
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_NoInput)
+{
+ auto res = ToResultGet<int32_t>([](nsresult* aRv) -> int32_t {
+ *aRv = NS_OK;
+ return 42;
+ });
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_NoInput_Err)
+{
+ auto res = ToResultGet<int32_t>([](nsresult* aRv) -> int32_t {
+ *aRv = NS_ERROR_FAILURE;
+ return -1;
+ });
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_WithInput)
+{
+ auto res = ToResultGet<int32_t>(
+ [](int32_t aValue, nsresult* aRv) -> int32_t {
+ *aRv = NS_OK;
+ return aValue * 2;
+ },
+ 42);
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 84);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_WithInput_Err)
+{
+ auto res = ToResultGet<int32_t>(
+ [](int32_t aValue, nsresult* aRv) -> int32_t {
+ *aRv = NS_ERROR_FAILURE;
+ return -1;
+ },
+ 42);
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_NoInput_Macro_Typed)
+{
+ auto res = MOZ_TO_RESULT_GET_TYPED(int32_t, [](nsresult* aRv) -> int32_t {
+ *aRv = NS_OK;
+ return 42;
+ });
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_NoInput_Macro_Typed_Parens)
+{
+ auto res =
+ MOZ_TO_RESULT_GET_TYPED((std::pair<int32_t, int32_t>),
+ [](nsresult* aRv) -> std::pair<int32_t, int32_t> {
+ *aRv = NS_OK;
+ return std::pair{42, 42};
+ });
+
+ static_assert(std::is_same_v<decltype(res),
+ Result<std::pair<int32_t, int32_t>, nsresult>>);
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), (std::pair{42, 42}));
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_NoInput_Err_Macro_Typed)
+{
+ auto res = MOZ_TO_RESULT_GET_TYPED(int32_t, [](nsresult* aRv) -> int32_t {
+ *aRv = NS_ERROR_FAILURE;
+ return -1;
+ });
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_WithInput_Macro_Typed)
+{
+ auto res = MOZ_TO_RESULT_GET_TYPED(
+ int32_t,
+ [](int32_t aValue, nsresult* aRv) -> int32_t {
+ *aRv = NS_OK;
+ return aValue * 2;
+ },
+ 42);
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 84);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_WithInput_Err_Macro_Typed)
+{
+ auto res = MOZ_TO_RESULT_GET_TYPED(
+ int32_t,
+ [](int32_t aValue, nsresult* aRv) -> int32_t {
+ *aRv = NS_ERROR_FAILURE;
+ return -1;
+ },
+ 42);
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultInvoke, Lambda_NoInput_Complex)
+{
+ TestClass foo;
+
+ // success
+ {
+ auto valOrErr =
+ ToResultInvoke<std::pair<int, int>>([&foo](std::pair<int, int>* out) {
+ return foo.NonOverloadedNoInputComplex(out);
+ });
+ static_assert(std::is_same_v<decltype(valOrErr),
+ Result<std::pair<int, int>, nsresult>>);
+ ASSERT_TRUE(valOrErr.isOk());
+ ASSERT_EQ((std::pair{TestClass::kTestValue, TestClass::kTestValue}),
+ valOrErr.unwrap());
+ }
+
+ // failure
+ {
+ auto valOrErr =
+ ToResultInvoke<std::pair<int, int>>([&foo](std::pair<int, int>* out) {
+ return foo.NonOverloadedNoInputFailsComplex(out);
+ });
+ static_assert(std::is_same_v<decltype(valOrErr),
+ Result<std::pair<int, int>, nsresult>>);
+ ASSERT_TRUE(valOrErr.isErr());
+ ASSERT_EQ(NS_ERROR_FAILURE, valOrErr.unwrapErr());
+ }
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultInvoke,
+ Lambda_NoInput_Complex_Macro_Typed)
+{
+ TestClass foo;
+
+ // success
+ {
+ auto valOrErr = MOZ_TO_RESULT_INVOKE_TYPED(
+ (std::pair<int, int>), [&foo](std::pair<int, int>* out) {
+ return foo.NonOverloadedNoInputComplex(out);
+ });
+ static_assert(std::is_same_v<decltype(valOrErr),
+ Result<std::pair<int, int>, nsresult>>);
+ ASSERT_TRUE(valOrErr.isOk());
+ ASSERT_EQ((std::pair{TestClass::kTestValue, TestClass::kTestValue}),
+ valOrErr.unwrap());
+ }
+
+ // failure
+ {
+ auto valOrErr = MOZ_TO_RESULT_INVOKE_TYPED(
+ (std::pair<int, int>), [&foo](std::pair<int, int>* out) {
+ return foo.NonOverloadedNoInputFailsComplex(out);
+ });
+ static_assert(std::is_same_v<decltype(valOrErr),
+ Result<std::pair<int, int>, nsresult>>);
+ ASSERT_TRUE(valOrErr.isErr());
+ ASSERT_EQ(NS_ERROR_FAILURE, valOrErr.unwrapErr());
+ }
+}
diff --git a/dom/quota/test/gtest/TestScopedLogExtraInfo.cpp b/dom/quota/test/gtest/TestScopedLogExtraInfo.cpp
new file mode 100644
index 0000000000..00a3393844
--- /dev/null
+++ b/dom/quota/test/gtest/TestScopedLogExtraInfo.cpp
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/quota/ScopedLogExtraInfo.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla::dom::quota;
+
+TEST(DOM_Quota_ScopedLogExtraInfo, AddAndRemove)
+{
+ static constexpr auto text = "foo"_ns;
+
+ {
+ const auto extraInfo =
+ ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, text};
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap();
+
+ EXPECT_EQ(text, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery));
+#endif
+ }
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap();
+
+ EXPECT_EQ(0u, extraInfoMap.count(ScopedLogExtraInfo::kTagQuery));
+#endif
+}
+
+TEST(DOM_Quota_ScopedLogExtraInfo, Nested)
+{
+ static constexpr auto text = "foo"_ns;
+ static constexpr auto nestedText = "bar"_ns;
+
+ {
+ const auto extraInfo =
+ ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, text};
+
+ {
+ const auto extraInfo =
+ ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, nestedText};
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap();
+ EXPECT_EQ(nestedText, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery));
+#endif
+ }
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap();
+ EXPECT_EQ(text, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery));
+#endif
+ }
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap();
+
+ EXPECT_EQ(0u, extraInfoMap.count(ScopedLogExtraInfo::kTagQuery));
+#endif
+}
diff --git a/dom/quota/test/gtest/TestUsageInfo.cpp b/dom/quota/test/gtest/TestUsageInfo.cpp
new file mode 100644
index 0000000000..124783b715
--- /dev/null
+++ b/dom/quota/test/gtest/TestUsageInfo.cpp
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/quota/UsageInfo.h"
+
+#include "gtest/gtest.h"
+
+#include <cstdint>
+#include <memory>
+#include <ostream>
+#include <utility>
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/fallible.h"
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+namespace {
+constexpr uint64_t kTestValue = 42;
+constexpr uint64_t kTestValueZero = 0;
+} // namespace
+
+TEST(DOM_Quota_UsageInfo, DefaultConstructed)
+{
+ const UsageInfo usageInfo;
+ EXPECT_EQ(Nothing(), usageInfo.FileUsage());
+ EXPECT_EQ(Nothing(), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, FileOnly)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(kTestValue), usageInfo.FileUsage());
+ EXPECT_EQ(Some(kTestValue), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, DatabaseOnly)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += DatabaseUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Nothing(), usageInfo.FileUsage());
+ EXPECT_EQ(Some(kTestValue), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, FileOnly_Zero)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(kTestValueZero));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(kTestValueZero), usageInfo.FileUsage());
+ EXPECT_EQ(Some(kTestValueZero), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, DatabaseOnly_Zero)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += DatabaseUsageType(Some(kTestValueZero));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Nothing(), usageInfo.FileUsage());
+ EXPECT_EQ(Some(kTestValueZero), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, Both)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(kTestValue));
+ usageInfo += DatabaseUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(kTestValue), usageInfo.FileUsage());
+ EXPECT_EQ(Some(2 * kTestValue), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, Both_Zero)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(kTestValueZero));
+ usageInfo += DatabaseUsageType(Some(kTestValueZero));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(kTestValueZero), usageInfo.FileUsage());
+ EXPECT_EQ(Some(kTestValueZero), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, CapCombined)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(UINT64_MAX));
+ usageInfo += DatabaseUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(UINT64_MAX), usageInfo.FileUsage());
+ EXPECT_EQ(Some(UINT64_MAX), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, CapFileUsage)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(UINT64_MAX));
+ usageInfo += FileUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(UINT64_MAX), usageInfo.FileUsage());
+ EXPECT_EQ(Some(UINT64_MAX), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, CapDatabaseUsage)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += DatabaseUsageType(Some(UINT64_MAX));
+ usageInfo += DatabaseUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Nothing(), usageInfo.FileUsage());
+ EXPECT_EQ(Some(UINT64_MAX), usageInfo.TotalUsage());
+}
diff --git a/dom/quota/test/gtest/moz.build b/dom/quota/test/gtest/moz.build
new file mode 100644
index 0000000000..30ca99b254
--- /dev/null
+++ b/dom/quota/test/gtest/moz.build
@@ -0,0 +1,44 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.dom.quota += [
+ "QuotaTestChild.h",
+ "QuotaTestParent.h",
+]
+
+EXPORTS.mozilla.dom.quota.test += [
+ "QuotaManagerDependencyFixture.h",
+]
+
+UNIFIED_SOURCES = [
+ "Common.cpp",
+ "QuotaManagerDependencyFixture.cpp",
+ "TestCheckedUnsafePtr.cpp",
+ "TestClientUsageArray.cpp",
+ "TestEncryptedStream.cpp",
+ "TestFileOutputStream.cpp",
+ "TestFlatten.cpp",
+ "TestForwardDecls.cpp",
+ "TestPersistenceType.cpp",
+ "TestQMResult.cpp",
+ "TestQuotaCommon.cpp",
+ "TestQuotaManager.cpp",
+ "TestResultExtensions.cpp",
+ "TestScopedLogExtraInfo.cpp",
+ "TestUsageInfo.cpp",
+]
+
+IPDL_SOURCES += [
+ "PQuotaTest.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
+
+LOCAL_INCLUDES += [
+ "/dom/quota",
+]
diff --git a/dom/quota/test/mochitest/helpers.js b/dom/quota/test/mochitest/helpers.js
new file mode 100644
index 0000000000..a6f4ad56f7
--- /dev/null
+++ b/dom/quota/test/mochitest/helpers.js
@@ -0,0 +1,312 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// The path to the top level directory.
+const depth = "../../../../";
+
+var testGenerator;
+var testHarnessGenerator;
+var workerScriptPaths = [];
+
+loadScript("dom/quota/test/common/mochitest.js");
+
+function loadScript(path) {
+ const url = new URL(depth + path, window.location.href);
+ SpecialPowers.Services.scriptloader.loadSubScript(url.href, this);
+}
+
+function loadWorkerScript(path) {
+ const url = new URL(depth + path, window.location.href);
+ workerScriptPaths.push(url.href);
+}
+
+function* testHarnessSteps() {
+ function nextTestHarnessStep(val) {
+ testHarnessGenerator.next(val);
+ }
+
+ info("Enabling storage testing");
+
+ enableStorageTesting().then(nextTestHarnessStep);
+ yield undefined;
+
+ info("Pushing preferences");
+
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [["dom.storageManager.prompt.testing", true]],
+ },
+ nextTestHarnessStep
+ );
+ yield undefined;
+
+ info("Clearing old databases");
+
+ clearAllDatabases(nextTestHarnessStep);
+ yield undefined;
+
+ info("Running test in given scopes");
+
+ if (workerScriptPaths.length) {
+ info("Running test in a worker");
+
+ let workerScriptBlob = new Blob(["(" + workerScript.toString() + ")();"], {
+ type: "text/javascript",
+ });
+ let workerScriptURL = URL.createObjectURL(workerScriptBlob);
+
+ let worker = new Worker(workerScriptURL);
+
+ worker.onerror = function (event) {
+ ok(false, "Worker had an error: " + event.message);
+ worker.terminate();
+ nextTestHarnessStep();
+ };
+
+ worker.onmessage = function (event) {
+ let message = event.data;
+ switch (message.op) {
+ case "ok":
+ ok(message.condition, message.name + " - " + message.diag);
+ break;
+
+ case "todo":
+ todo(message.condition, message.name, message.diag);
+ break;
+
+ case "info":
+ info(message.msg);
+ break;
+
+ case "ready":
+ worker.postMessage({ op: "load", files: workerScriptPaths });
+ break;
+
+ case "loaded":
+ worker.postMessage({ op: "start" });
+ break;
+
+ case "done":
+ ok(true, "Worker finished");
+ nextTestHarnessStep();
+ break;
+
+ case "clearAllDatabases":
+ clearAllDatabases(function () {
+ worker.postMessage({ op: "clearAllDatabasesDone" });
+ });
+ break;
+
+ default:
+ ok(
+ false,
+ "Received a bad message from worker: " + JSON.stringify(message)
+ );
+ nextTestHarnessStep();
+ }
+ };
+
+ URL.revokeObjectURL(workerScriptURL);
+
+ yield undefined;
+
+ worker.terminate();
+ worker = null;
+
+ clearAllDatabases(nextTestHarnessStep);
+ yield undefined;
+ }
+
+ info("Running test in main thread");
+
+ // Now run the test script in the main thread.
+ if (testSteps.constructor.name === "AsyncFunction") {
+ SimpleTest.registerCleanupFunction(async function () {
+ await requestFinished(clearAllDatabases());
+ });
+
+ add_task(testSteps);
+ } else {
+ testGenerator = testSteps();
+ testGenerator.next();
+
+ yield undefined;
+ }
+}
+
+if (!window.runTest) {
+ window.runTest = function () {
+ SimpleTest.waitForExplicitFinish();
+ testHarnessGenerator = testHarnessSteps();
+ testHarnessGenerator.next();
+ };
+}
+
+function finishTest() {
+ SimpleTest.executeSoon(function () {
+ clearAllDatabases(function () {
+ SimpleTest.finish();
+ });
+ });
+}
+
+function grabArgAndContinueHandler(arg) {
+ testGenerator.next(arg);
+}
+
+function continueToNextStep() {
+ SimpleTest.executeSoon(function () {
+ testGenerator.next();
+ });
+}
+
+function continueToNextStepSync() {
+ testGenerator.next();
+}
+
+function workerScript() {
+ "use strict";
+
+ self.testGenerator = null;
+
+ self.repr = function (_thing_) {
+ if (typeof _thing_ == "undefined") {
+ return "undefined";
+ }
+
+ let str;
+
+ try {
+ str = _thing_ + "";
+ } catch (e) {
+ return "[" + typeof _thing_ + "]";
+ }
+
+ if (typeof _thing_ == "function") {
+ str = str.replace(/^\s+/, "");
+ let idx = str.indexOf("{");
+ if (idx != -1) {
+ str = str.substr(0, idx) + "{...}";
+ }
+ }
+
+ return str;
+ };
+
+ self.ok = function (_condition_, _name_, _diag_) {
+ self.postMessage({
+ op: "ok",
+ condition: !!_condition_,
+ name: _name_,
+ diag: _diag_,
+ });
+ };
+
+ self.is = function (_a_, _b_, _name_) {
+ let pass = _a_ == _b_;
+ let diag = pass ? "" : "got " + repr(_a_) + ", expected " + repr(_b_);
+ ok(pass, _name_, diag);
+ };
+
+ self.isnot = function (_a_, _b_, _name_) {
+ let pass = _a_ != _b_;
+ let diag = pass ? "" : "didn't expect " + repr(_a_) + ", but got it";
+ ok(pass, _name_, diag);
+ };
+
+ self.todo = function (_condition_, _name_, _diag_) {
+ self.postMessage({
+ op: "todo",
+ condition: !!_condition_,
+ name: _name_,
+ diag: _diag_,
+ });
+ };
+
+ self.info = function (_msg_) {
+ self.postMessage({ op: "info", msg: _msg_ });
+ };
+
+ self.executeSoon = function (_fun_) {
+ var channel = new MessageChannel();
+ channel.port1.postMessage("");
+ channel.port2.onmessage = function (event) {
+ _fun_();
+ };
+ };
+
+ self.finishTest = function () {
+ self.postMessage({ op: "done" });
+ };
+
+ self.grabArgAndContinueHandler = function (_arg_) {
+ testGenerator.next(_arg_);
+ };
+
+ self.continueToNextStep = function () {
+ executeSoon(function () {
+ testGenerator.next();
+ });
+ };
+
+ self.continueToNextStepSync = function () {
+ testGenerator.next();
+ };
+
+ self._clearAllDatabasesCallback = undefined;
+ self.clearAllDatabases = function (_callback_) {
+ self._clearAllDatabasesCallback = _callback_;
+ self.postMessage({ op: "clearAllDatabases" });
+ };
+
+ self.onerror = function (_message_, _file_, _line_) {
+ ok(
+ false,
+ "Worker: uncaught exception [" +
+ _file_ +
+ ":" +
+ _line_ +
+ "]: '" +
+ _message_ +
+ "'"
+ );
+ self.finishTest();
+ self.close();
+ return true;
+ };
+
+ self.onmessage = function (_event_) {
+ let message = _event_.data;
+ switch (message.op) {
+ case "load":
+ info("Worker: loading " + JSON.stringify(message.files));
+ self.importScripts(message.files);
+ self.postMessage({ op: "loaded" });
+ break;
+
+ case "start":
+ executeSoon(function () {
+ info("Worker: starting tests");
+ testGenerator = testSteps();
+ testGenerator.next();
+ });
+ break;
+
+ case "clearAllDatabasesDone":
+ info("Worker: all databases are cleared");
+ if (self._clearAllDatabasesCallback) {
+ self._clearAllDatabasesCallback();
+ }
+ break;
+
+ default:
+ throw new Error(
+ "Received a bad message from parent: " + JSON.stringify(message)
+ );
+ }
+ };
+
+ self.postMessage({ op: "ready" });
+}
diff --git a/dom/quota/test/mochitest/mochitest.ini b/dom/quota/test/mochitest/mochitest.ini
new file mode 100644
index 0000000000..84b7393083
--- /dev/null
+++ b/dom/quota/test/mochitest/mochitest.ini
@@ -0,0 +1,18 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+support-files =
+ helpers.js
+
+[test_simpledb.html]
+skip-if =
+ http3
+[test_storage_manager_persist_allow.html]
+fail-if = xorigin
+scheme=https
+[test_storage_manager_persist_deny.html]
+scheme=https
+[test_storage_manager_persisted.html]
+scheme=https
diff --git a/dom/quota/test/mochitest/test_simpledb.html b/dom/quota/test/mochitest/test_simpledb.html
new file mode 100644
index 0000000000..29ca8be65e
--- /dev/null
+++ b/dom/quota/test/mochitest/test_simpledb.html
@@ -0,0 +1,21 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>SimpleDB Test</title>
+
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script type="text/javascript" src="helpers.js"></script>
+ <script type="text/javascript">
+ loadScript("dom/quota/test/common/test_simpledb.js");
+ </script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
diff --git a/dom/quota/test/mochitest/test_storage_manager_persist_allow.html b/dom/quota/test/mochitest/test_storage_manager_persist_allow.html
new file mode 100644
index 0000000000..8477630f21
--- /dev/null
+++ b/dom/quota/test/mochitest/test_storage_manager_persist_allow.html
@@ -0,0 +1,21 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Allow Persist Prompt for StorageManager</title>
+
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script type="text/javascript" src="helpers.js"></script>
+ <script type="text/javascript">
+ loadScript("dom/quota/test/common/test_storage_manager_persist_allow.js");
+ </script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
diff --git a/dom/quota/test/mochitest/test_storage_manager_persist_deny.html b/dom/quota/test/mochitest/test_storage_manager_persist_deny.html
new file mode 100644
index 0000000000..2b0fab4423
--- /dev/null
+++ b/dom/quota/test/mochitest/test_storage_manager_persist_deny.html
@@ -0,0 +1,21 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Deny Persist Prompt for StorageManager</title>
+
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script type="text/javascript" src="helpers.js"></script>
+ <script type="text/javascript">
+ loadScript("dom/quota/test/common/test_storage_manager_persist_deny.js");
+ </script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
diff --git a/dom/quota/test/mochitest/test_storage_manager_persisted.html b/dom/quota/test/mochitest/test_storage_manager_persisted.html
new file mode 100644
index 0000000000..6e03f33e2d
--- /dev/null
+++ b/dom/quota/test/mochitest/test_storage_manager_persisted.html
@@ -0,0 +1,24 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Storage Manager Persisted Test</title>
+
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script type="text/javascript" src="helpers.js"></script>
+ <script type="text/javascript">
+ const path = "dom/quota/test/common/test_storage_manager_persisted.js";
+
+ loadScript(path);
+ loadWorkerScript(path);
+ </script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
diff --git a/dom/quota/test/modules/content/.eslintrc.js b/dom/quota/test/modules/content/.eslintrc.js
new file mode 100644
index 0000000000..f4482935e5
--- /dev/null
+++ b/dom/quota/test/modules/content/.eslintrc.js
@@ -0,0 +1,24 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+module.exports = {
+ overrides: [
+ {
+ files: [
+ "Assert.js",
+ "ModuleLoader.js",
+ "StorageUtils.js",
+ "Utils.js",
+ "UtilsParent.js",
+ "WorkerDriver.js",
+ ],
+ parserOptions: {
+ sourceType: "module",
+ },
+ },
+ ],
+};
diff --git a/dom/quota/test/modules/content/Assert.js b/dom/quota/test/modules/content/Assert.js
new file mode 100644
index 0000000000..e2c8df19c8
--- /dev/null
+++ b/dom/quota/test/modules/content/Assert.js
@@ -0,0 +1,10 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Just a wrapper around SimpleTest related functions for now.
+export const Assert = {
+ ok,
+ equal: is,
+};
diff --git a/dom/quota/test/modules/content/ModuleLoader.js b/dom/quota/test/modules/content/ModuleLoader.js
new file mode 100644
index 0000000000..7e2b7cd89d
--- /dev/null
+++ b/dom/quota/test/modules/content/ModuleLoader.js
@@ -0,0 +1,61 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+export function ModuleLoader(base, depth, proto) {
+ const modules = {};
+
+ const principal = SpecialPowers.wrap(document).nodePrincipal;
+
+ const sharedGlobalSandbox = SpecialPowers.Cu.Sandbox(principal, {
+ invisibleToDebugger: true,
+ sandboxName: "FS Module Loader",
+ sandboxPrototype: proto,
+ wantComponents: false,
+ wantGlobalProperties: [],
+ wantXrays: false,
+ });
+
+ const require = async function (id) {
+ if (modules[id]) {
+ return modules[id].exported_symbols;
+ }
+
+ const url = new URL(depth + id, base);
+
+ const module = Object.create(null, {
+ exported_symbols: {
+ configurable: false,
+ enumerable: true,
+ value: Object.create(null),
+ writable: true,
+ },
+ });
+
+ modules[id] = module;
+
+ const properties = {
+ require_module: require,
+ exported_symbols: module.exported_symbols,
+ };
+
+ // Create a new object in this sandbox, that will be used as the scope
+ // object for this particular module.
+ const sandbox = sharedGlobalSandbox.Object();
+ Object.assign(sandbox, properties);
+
+ SpecialPowers.Services.scriptloader.loadSubScript(url.href, sandbox);
+
+ return module.exported_symbols;
+ };
+
+ const returnObj = {
+ require: {
+ enumerable: true,
+ value: require,
+ },
+ };
+
+ return Object.create(null, returnObj);
+}
diff --git a/dom/quota/test/modules/content/StorageUtils.js b/dom/quota/test/modules/content/StorageUtils.js
new file mode 100644
index 0000000000..bb264966af
--- /dev/null
+++ b/dom/quota/test/modules/content/StorageUtils.js
@@ -0,0 +1,67 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+class RequestError extends Error {
+ constructor(resultCode, resultName) {
+ super(`Request failed (code: ${resultCode}, name: ${resultName})`);
+ this.name = "RequestError";
+ this.resultCode = resultCode;
+ this.resultName = resultName;
+ }
+}
+
+export async function setStoragePrefs(optionalPrefsToSet) {
+ const prefsToSet = [
+ // Not needed right now, but might be needed in future.
+ // ["dom.quotaManager.testing", true],
+ ];
+
+ if (SpecialPowers.Services.appinfo.OS === "WINNT") {
+ prefsToSet.push(["dom.quotaManager.useDOSDevicePathSyntax", true]);
+ }
+
+ if (optionalPrefsToSet) {
+ prefsToSet.push(...optionalPrefsToSet);
+ }
+
+ await SpecialPowers.pushPrefEnv({ set: prefsToSet });
+}
+
+export async function getUsageForOrigin(principal, fromMemory) {
+ const request = SpecialPowers.Services.qms.getUsageForPrincipal(
+ principal,
+ function () {},
+ fromMemory
+ );
+
+ await new Promise(function (resolve) {
+ request.callback = SpecialPowers.wrapCallback(function () {
+ resolve();
+ });
+ });
+
+ if (request.resultCode != SpecialPowers.Cr.NS_OK) {
+ throw new RequestError(request.resultCode, request.resultName);
+ }
+
+ return request.result;
+}
+
+export async function clearStoragesForOrigin(principal) {
+ const request =
+ SpecialPowers.Services.qms.clearStoragesForPrincipal(principal);
+
+ await new Promise(function (resolve) {
+ request.callback = SpecialPowers.wrapCallback(function () {
+ resolve();
+ });
+ });
+
+ if (request.resultCode != SpecialPowers.Cr.NS_OK) {
+ throw new RequestError(request.resultCode, request.resultName);
+ }
+
+ return request.result;
+}
diff --git a/dom/quota/test/modules/content/Utils.js b/dom/quota/test/modules/content/Utils.js
new file mode 100644
index 0000000000..3f011e32f4
--- /dev/null
+++ b/dom/quota/test/modules/content/Utils.js
@@ -0,0 +1,14 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+import { getUsageForOrigin } from "/tests/dom/quota/test/modules/StorageUtils.js";
+
+export const Utils = {
+ async getCachedOriginUsage() {
+ const principal = SpecialPowers.wrap(document).nodePrincipal;
+ const result = await getUsageForOrigin(principal, true);
+ return result.usage;
+ },
+};
diff --git a/dom/quota/test/modules/content/UtilsParent.js b/dom/quota/test/modules/content/UtilsParent.js
new file mode 100644
index 0000000000..56859500be
--- /dev/null
+++ b/dom/quota/test/modules/content/UtilsParent.js
@@ -0,0 +1,21 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+import { Utils } from "/tests/dom/quota/test/modules/Utils.js";
+
+export const UtilsParent = {
+ async OnMessageReceived(worker, msg) {
+ switch (msg.op) {
+ case "getCachedOriginUsage": {
+ const result = await Utils.getCachedOriginUsage();
+ worker.postMessage(result);
+ break;
+ }
+
+ default:
+ throw new Error(`Unknown op ${msg.op}`);
+ }
+ },
+};
diff --git a/dom/quota/test/modules/content/WorkerDriver.js b/dom/quota/test/modules/content/WorkerDriver.js
new file mode 100644
index 0000000000..52ca382c25
--- /dev/null
+++ b/dom/quota/test/modules/content/WorkerDriver.js
@@ -0,0 +1,68 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+export async function runTestInWorker(script, base, listener) {
+ return new Promise(function (resolve) {
+ const globalHeadUrl = new URL(
+ "/tests/dom/quota/test/modules/worker/head.js",
+ base
+ );
+
+ let modules = {};
+
+ const worker = new Worker(globalHeadUrl.href);
+
+ worker.onmessage = async function (event) {
+ const data = event.data;
+ const moduleName = data.moduleName;
+ const objectName = data.objectName;
+
+ if (moduleName && objectName) {
+ if (!modules[moduleName]) {
+ // eslint-disable-next-line no-unsanitized/method
+ modules[moduleName] = await import(
+ "/tests/dom/quota/test/modules/" + moduleName + ".js"
+ );
+ }
+ await modules[moduleName][objectName].OnMessageReceived(worker, data);
+ return;
+ }
+
+ switch (data.op) {
+ case "ok":
+ listener.onOk(data.value, data.message);
+ break;
+
+ case "is":
+ listener.onIs(data.a, data.b, data.message);
+ break;
+
+ case "info":
+ listener.onInfo(data.message);
+ break;
+
+ case "finish":
+ resolve();
+ break;
+
+ case "failure":
+ listener.onOk(false, "Worker had a failure: " + data.message);
+ resolve();
+ break;
+ }
+ };
+
+ worker.onerror = function (event) {
+ listener.onOk(false, "Worker had an error: " + event.data);
+ resolve();
+ };
+
+ const scriptUrl = new URL(script, base);
+
+ const localHeadUrl = new URL("head.js", scriptUrl);
+
+ worker.postMessage([localHeadUrl.href, scriptUrl.href]);
+ });
+}
diff --git a/dom/quota/test/modules/content/worker/.eslintrc.js b/dom/quota/test/modules/content/worker/.eslintrc.js
new file mode 100644
index 0000000000..a62b649451
--- /dev/null
+++ b/dom/quota/test/modules/content/worker/.eslintrc.js
@@ -0,0 +1,21 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+module.exports = {
+ env: {
+ worker: true,
+ },
+
+ overrides: [
+ {
+ files: ["Assert.js", "ModuleLoader.js", "Utils.js"],
+ parserOptions: {
+ sourceType: "script",
+ },
+ },
+ ],
+};
diff --git a/dom/quota/test/modules/content/worker/Assert.js b/dom/quota/test/modules/content/worker/Assert.js
new file mode 100644
index 0000000000..7c7e2683ea
--- /dev/null
+++ b/dom/quota/test/modules/content/worker/Assert.js
@@ -0,0 +1,22 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const Assert = {
+ ok(value, message) {
+ postMessage({
+ op: "ok",
+ value: !!value,
+ message,
+ });
+ },
+ equal(a, b, message) {
+ postMessage({
+ op: "is",
+ a,
+ b,
+ message,
+ });
+ },
+};
diff --git a/dom/quota/test/modules/content/worker/ModuleLoader.js b/dom/quota/test/modules/content/worker/ModuleLoader.js
new file mode 100644
index 0000000000..5354c26e34
--- /dev/null
+++ b/dom/quota/test/modules/content/worker/ModuleLoader.js
@@ -0,0 +1,52 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function ModuleLoader(base, depth) {
+ const modules = {};
+
+ const require = async function (id) {
+ if (modules[id]) {
+ return modules[id].exported_symbols;
+ }
+
+ const url = new URL(depth + id, base);
+
+ const module = Object.create(null, {
+ exported_symbols: {
+ configurable: false,
+ enumerable: true,
+ value: Object.create(null),
+ writable: true,
+ },
+ });
+
+ modules[id] = module;
+
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", url.href, false);
+ xhr.responseType = "text";
+ xhr.send();
+
+ let source = xhr.responseText;
+
+ let code = new Function(
+ "require_module",
+ "exported_symbols",
+ `eval(arguments[2] + "\\n//# sourceURL=" + arguments[3] + "\\n")`
+ );
+ code(require, module.exported_symbols, source, url.href);
+
+ return module.exported_symbols;
+ };
+
+ const returnObj = {
+ require: {
+ enumerable: true,
+ value: require,
+ },
+ };
+
+ return Object.create(null, returnObj);
+}
diff --git a/dom/quota/test/modules/content/worker/Utils.js b/dom/quota/test/modules/content/worker/Utils.js
new file mode 100644
index 0000000000..0f8888fc00
--- /dev/null
+++ b/dom/quota/test/modules/content/worker/Utils.js
@@ -0,0 +1,27 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+let UtilsChild;
+
+async function ensureUtilsChild() {
+ if (UtilsChild) {
+ return;
+ }
+
+ const { UtilsChild: importedUtilsChild } = await import(
+ "/tests/dom/quota/test/modules/worker/UtilsChild.mjs"
+ );
+
+ UtilsChild = importedUtilsChild;
+}
+
+const Utils = {
+ async getCachedOriginUsage() {
+ await ensureUtilsChild();
+
+ const result = await UtilsChild.getCachedOriginUsage();
+ return result;
+ },
+};
diff --git a/dom/quota/test/modules/content/worker/UtilsChild.mjs b/dom/quota/test/modules/content/worker/UtilsChild.mjs
new file mode 100644
index 0000000000..db5757bff0
--- /dev/null
+++ b/dom/quota/test/modules/content/worker/UtilsChild.mjs
@@ -0,0 +1,22 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+export const UtilsChild = {
+ async getCachedOriginUsage() {
+ postMessage({
+ moduleName: "UtilsParent",
+ objectName: "UtilsParent",
+ op: "getCachedOriginUsage",
+ });
+
+ return new Promise(function (resolve) {
+ addEventListener("message", async function onMessage(event) {
+ removeEventListener("message", onMessage);
+ const data = event.data;
+ resolve(data);
+ });
+ });
+ },
+};
diff --git a/dom/quota/test/modules/content/worker/head.js b/dom/quota/test/modules/content/worker/head.js
new file mode 100644
index 0000000000..ab7a916d22
--- /dev/null
+++ b/dom/quota/test/modules/content/worker/head.js
@@ -0,0 +1,55 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const Cr = {
+ NS_ERROR_NOT_IMPLEMENTED: 2147500033,
+};
+
+function add_task(func) {
+ if (!add_task.tasks) {
+ add_task.tasks = [];
+ add_task.index = 0;
+ }
+
+ add_task.tasks.push(func);
+}
+
+addEventListener("message", async function onMessage(event) {
+ function info(message) {
+ postMessage({ op: "info", message });
+ }
+
+ function executeSoon(callback) {
+ const channel = new MessageChannel();
+ channel.port1.postMessage("");
+ channel.port2.onmessage = function () {
+ callback();
+ };
+ }
+
+ function runNextTest() {
+ if (add_task.index < add_task.tasks.length) {
+ const task = add_task.tasks[add_task.index++];
+ info("add_task | Entering test " + task.name);
+ task()
+ .then(function () {
+ executeSoon(runNextTest);
+ info("add_task | Leaving test " + task.name);
+ })
+ .catch(function (ex) {
+ postMessage({ op: "failure", message: "" + ex });
+ });
+ } else {
+ postMessage({ op: "finish" });
+ }
+ }
+
+ removeEventListener("message", onMessage);
+
+ const data = event.data;
+ importScripts(...data);
+
+ executeSoon(runNextTest);
+});
diff --git a/dom/quota/test/modules/system/ModuleLoader.sys.mjs b/dom/quota/test/modules/system/ModuleLoader.sys.mjs
new file mode 100644
index 0000000000..d4f8f51c11
--- /dev/null
+++ b/dom/quota/test/modules/system/ModuleLoader.sys.mjs
@@ -0,0 +1,63 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+export function ModuleLoader(base, depth, proto) {
+ const modules = {};
+
+ const principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(
+ Ci.nsIPrincipal
+ );
+
+ const sharedGlobalsandbox = Cu.Sandbox(principal, {
+ invisibleToDebugger: true,
+ sandboxName: "FS Module Loader",
+ sandboxPrototype: proto,
+ wantComponents: false,
+ wantGlobalProperties: [],
+ wantXrays: false,
+ });
+
+ const require = async function (id) {
+ if (modules[id]) {
+ return modules[id].exported_symbols;
+ }
+
+ const url = new URL(depth + id, base);
+
+ const module = Object.create(null, {
+ exported_symbols: {
+ configurable: false,
+ enumerable: true,
+ value: Object.create(null),
+ writable: true,
+ },
+ });
+
+ modules[id] = module;
+
+ const properties = {
+ require_module: require,
+ exported_symbols: module.exported_symbols,
+ };
+
+ // Create a new object in this sandbox, that will be used as the scope
+ // object for this particular module.
+ const sandbox = sharedGlobalsandbox.Object();
+ Object.assign(sandbox, properties);
+
+ Services.scriptloader.loadSubScript(url.href, sandbox);
+
+ return module.exported_symbols;
+ };
+
+ const returnObj = {
+ require: {
+ enumerable: true,
+ value: require,
+ },
+ };
+
+ return Object.create(null, returnObj);
+}
diff --git a/dom/quota/test/modules/system/StorageUtils.sys.mjs b/dom/quota/test/modules/system/StorageUtils.sys.mjs
new file mode 100644
index 0000000000..0e87906035
--- /dev/null
+++ b/dom/quota/test/modules/system/StorageUtils.sys.mjs
@@ -0,0 +1,101 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+class RequestError extends Error {
+ constructor(resultCode, resultName) {
+ super(`Request failed (code: ${resultCode}, name: ${resultName})`);
+ this.name = "RequestError";
+ this.resultCode = resultCode;
+ this.resultName = resultName;
+ }
+}
+
+export function setStoragePrefs(optionalPrefsToSet) {
+ const prefsToSet = [["dom.quotaManager.testing", true]];
+
+ if (Services.appinfo.OS === "WINNT") {
+ prefsToSet.push(["dom.quotaManager.useDOSDevicePathSyntax", true]);
+ }
+
+ if (optionalPrefsToSet) {
+ prefsToSet.push(...optionalPrefsToSet);
+ }
+
+ for (const pref of prefsToSet) {
+ Services.prefs.setBoolPref(pref[0], pref[1]);
+ }
+}
+
+export function clearStoragePrefs(optionalPrefsToClear) {
+ const prefsToClear = [
+ "dom.quotaManager.testing",
+ "dom.simpleDB.enabled",
+ "dom.storageManager.enabled",
+ ];
+
+ if (Services.appinfo.OS === "WINNT") {
+ prefsToClear.push("dom.quotaManager.useDOSDevicePathSyntax");
+ }
+
+ if (optionalPrefsToClear) {
+ prefsToClear.push(...optionalPrefsToClear);
+ }
+
+ for (const pref of prefsToClear) {
+ Services.prefs.clearUserPref(pref);
+ }
+}
+
+export async function getUsageForOrigin(principal, fromMemory) {
+ const request = Services.qms.getUsageForPrincipal(
+ principal,
+ function () {},
+ fromMemory
+ );
+
+ await new Promise(function (resolve) {
+ request.callback = function () {
+ resolve();
+ };
+ });
+
+ if (request.resultCode != Cr.NS_OK) {
+ throw new RequestError(request.resultCode, request.resultName);
+ }
+
+ return request.result;
+}
+
+export async function clearStoragesForOrigin(principal) {
+ const request = Services.qms.clearStoragesForPrincipal(principal);
+
+ await new Promise(function (resolve) {
+ request.callback = function () {
+ resolve();
+ };
+ });
+
+ if (request.resultCode != Cr.NS_OK) {
+ throw new RequestError(request.resultCode, request.resultName);
+ }
+
+ return request.result;
+}
+
+export async function resetStorage() {
+ const request = Services.qms.reset();
+
+ await new Promise(function (resolve) {
+ request.callback = function () {
+ resolve();
+ };
+ });
+
+ if (request.resultCode != Cr.NS_OK) {
+ throw new RequestError(request.resultCode, request.resultName);
+ }
+
+ return request.result;
+}
diff --git a/dom/quota/test/modules/system/Utils.sys.mjs b/dom/quota/test/modules/system/Utils.sys.mjs
new file mode 100644
index 0000000000..8c5430aa06
--- /dev/null
+++ b/dom/quota/test/modules/system/Utils.sys.mjs
@@ -0,0 +1,38 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+import {
+ getUsageForOrigin,
+ resetStorage,
+} from "resource://testing-common/dom/quota/test/modules/StorageUtils.sys.mjs";
+
+export const Utils = {
+ async getCachedOriginUsage() {
+ const principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(
+ Ci.nsIPrincipal
+ );
+ const result = await getUsageForOrigin(principal, true);
+ return result.usage;
+ },
+
+ async shrinkStorageSize(size) {
+ Services.prefs.setIntPref(
+ "dom.quotaManager.temporaryStorage.fixedLimit",
+ size
+ );
+
+ const result = await resetStorage();
+ return result;
+ },
+
+ async restoreStorageSize() {
+ Services.prefs.clearUserPref(
+ "dom.quotaManager.temporaryStorage.fixedLimit"
+ );
+
+ const result = await resetStorage();
+ return result;
+ },
+};
diff --git a/dom/quota/test/modules/system/UtilsParent.sys.mjs b/dom/quota/test/modules/system/UtilsParent.sys.mjs
new file mode 100644
index 0000000000..41ca7e2940
--- /dev/null
+++ b/dom/quota/test/modules/system/UtilsParent.sys.mjs
@@ -0,0 +1,32 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+import { Utils } from "resource://testing-common/dom/quota/test/modules/Utils.sys.mjs";
+
+export const UtilsParent = {
+ async OnMessageReceived(worker, msg) {
+ switch (msg.op) {
+ case "getCachedOriginUsage": {
+ const result = await Utils.getCachedOriginUsage();
+ worker.postMessage(result);
+ break;
+ }
+ case "shrinkStorageSize": {
+ const result = await Utils.shrinkStorageSize(msg.size);
+ worker.postMessage(result);
+ break;
+ }
+
+ case "restoreStorageSize": {
+ const result = await Utils.restoreStorageSize();
+ worker.postMessage(result);
+ break;
+ }
+
+ default:
+ throw new Error(`Unknown op ${msg.op}`);
+ }
+ },
+};
diff --git a/dom/quota/test/modules/system/WorkerDriver.sys.mjs b/dom/quota/test/modules/system/WorkerDriver.sys.mjs
new file mode 100644
index 0000000000..60e6790760
--- /dev/null
+++ b/dom/quota/test/modules/system/WorkerDriver.sys.mjs
@@ -0,0 +1,68 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+export async function runTestInWorker(script, base, listener) {
+ return new Promise(function (resolve) {
+ const globalHeadUrl = new URL(
+ "resource://testing-common/dom/quota/test/modules/worker/head.js"
+ );
+
+ let modules = {};
+
+ const worker = new Worker(globalHeadUrl.href);
+
+ worker.onmessage = async function (event) {
+ const data = event.data;
+ const moduleName = data.moduleName;
+ const objectName = data.objectName;
+
+ if (moduleName && objectName) {
+ if (!modules[moduleName]) {
+ modules[moduleName] = ChromeUtils.importESModule(
+ "resource://testing-common/dom/quota/test/modules/" +
+ moduleName +
+ ".sys.mjs"
+ );
+ }
+ await modules[moduleName][objectName].OnMessageReceived(worker, data);
+ return;
+ }
+
+ switch (data.op) {
+ case "ok":
+ listener.onOk(data.value, data.message);
+ break;
+
+ case "is":
+ listener.onIs(data.a, data.b, data.message);
+ break;
+
+ case "info":
+ listener.onInfo(data.message);
+ break;
+
+ case "finish":
+ resolve();
+ break;
+
+ case "failure":
+ listener.onOk(false, "Worker had a failure: " + data.message);
+ resolve();
+ break;
+ }
+ };
+
+ worker.onerror = function (event) {
+ listener.onOk(false, "Worker had an error: " + event.data);
+ resolve();
+ };
+
+ const scriptUrl = new URL(script, base);
+
+ const localHeadUrl = new URL("head.js", scriptUrl);
+
+ worker.postMessage([localHeadUrl.href, scriptUrl.href]);
+ });
+}
diff --git a/dom/quota/test/modules/system/worker/.eslintrc.js b/dom/quota/test/modules/system/worker/.eslintrc.js
new file mode 100644
index 0000000000..505e079c57
--- /dev/null
+++ b/dom/quota/test/modules/system/worker/.eslintrc.js
@@ -0,0 +1,21 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+module.exports = {
+ env: {
+ worker: true,
+ },
+
+ overrides: [
+ {
+ files: ["head.js"],
+ env: {
+ worker: true,
+ },
+ },
+ ],
+};
diff --git a/dom/quota/test/modules/system/worker/Assert.js b/dom/quota/test/modules/system/worker/Assert.js
new file mode 100644
index 0000000000..7c7e2683ea
--- /dev/null
+++ b/dom/quota/test/modules/system/worker/Assert.js
@@ -0,0 +1,22 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const Assert = {
+ ok(value, message) {
+ postMessage({
+ op: "ok",
+ value: !!value,
+ message,
+ });
+ },
+ equal(a, b, message) {
+ postMessage({
+ op: "is",
+ a,
+ b,
+ message,
+ });
+ },
+};
diff --git a/dom/quota/test/modules/system/worker/ModuleLoader.js b/dom/quota/test/modules/system/worker/ModuleLoader.js
new file mode 100644
index 0000000000..b79f3ff5b9
--- /dev/null
+++ b/dom/quota/test/modules/system/worker/ModuleLoader.js
@@ -0,0 +1,52 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function ModuleLoader(base, depth, proto) {
+ const modules = {};
+
+ const require = async function (id) {
+ if (modules[id]) {
+ return modules[id].exported_symbols;
+ }
+
+ const url = new URL(depth + id, base);
+
+ const module = Object.create(null, {
+ exported_symbols: {
+ configurable: false,
+ enumerable: true,
+ value: Object.create(null),
+ writable: true,
+ },
+ });
+
+ modules[id] = module;
+
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", url.href, false);
+ xhr.responseType = "text";
+ xhr.send();
+
+ let source = xhr.responseText;
+
+ let code = new Function(
+ "require_module",
+ "exported_symbols",
+ `eval(arguments[2] + "\\n//# sourceURL=" + arguments[3] + "\\n")`
+ );
+ code(require, module.exported_symbols, source, url.href);
+
+ return module.exported_symbols;
+ };
+
+ const returnObj = {
+ require: {
+ enumerable: true,
+ value: require,
+ },
+ };
+
+ return Object.create(null, returnObj);
+}
diff --git a/dom/quota/test/modules/system/worker/Utils.js b/dom/quota/test/modules/system/worker/Utils.js
new file mode 100644
index 0000000000..ca9b54a92a
--- /dev/null
+++ b/dom/quota/test/modules/system/worker/Utils.js
@@ -0,0 +1,55 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+let UtilsChild;
+
+async function ensureUtilsChild() {
+ if (UtilsChild) {
+ return;
+ }
+
+ try {
+ const { UtilsChild: importedUtilsChild } = await import(
+ "/dom/quota/test/modules/worker/UtilsChild.js"
+ );
+
+ UtilsChild = importedUtilsChild;
+
+ throw Error("Please switch to dynamic module import");
+ } catch (e) {
+ if (e.message == "Please switch to dynamic module import") {
+ throw e;
+ }
+
+ importScripts("/dom/quota/test/modules/worker/UtilsChild.js");
+
+ const { UtilsChild: importedUtilsChild } = globalThis.importUtilsChild();
+
+ UtilsChild = importedUtilsChild;
+ }
+}
+
+const Utils = {
+ async getCachedOriginUsage() {
+ await ensureUtilsChild();
+
+ const result = await UtilsChild.getCachedOriginUsage();
+ return result;
+ },
+
+ async shrinkStorageSize(size) {
+ await ensureUtilsChild();
+
+ const result = await UtilsChild.shrinkStorageSize(size);
+ return result;
+ },
+
+ async restoreStorageSize() {
+ await ensureUtilsChild();
+
+ const result = await UtilsChild.restoreStorageSize();
+ return result;
+ },
+};
diff --git a/dom/quota/test/modules/system/worker/UtilsChild.js b/dom/quota/test/modules/system/worker/UtilsChild.js
new file mode 100644
index 0000000000..4addd6b948
--- /dev/null
+++ b/dom/quota/test/modules/system/worker/UtilsChild.js
@@ -0,0 +1,56 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function _sendMessage(messageBody) {
+ const messageHeader = {
+ moduleName: "UtilsParent",
+ objectName: "UtilsParent",
+ };
+
+ const message = { ...messageHeader, ...messageBody };
+
+ postMessage(message);
+}
+
+function _recvMessage() {
+ return new Promise(function (resolve) {
+ addEventListener("message", async function onMessage(event) {
+ removeEventListener("message", onMessage);
+ const data = event.data;
+ resolve(data);
+ });
+ });
+}
+
+const _UtilsChild = {
+ async getCachedOriginUsage() {
+ _sendMessage({
+ op: "getCachedOriginUsage",
+ });
+
+ return _recvMessage();
+ },
+
+ async shrinkStorageSize(size) {
+ _sendMessage({
+ op: "shrinkStorageSize",
+ size,
+ });
+
+ return _recvMessage();
+ },
+
+ async restoreStorageSize() {
+ _sendMessage({
+ op: "restoreStorageSize",
+ });
+
+ return _recvMessage();
+ },
+};
+
+function importUtilsChild() {
+ return { UtilsChild: _UtilsChild };
+}
diff --git a/dom/quota/test/modules/system/worker/head.js b/dom/quota/test/modules/system/worker/head.js
new file mode 100644
index 0000000000..c415e0c56b
--- /dev/null
+++ b/dom/quota/test/modules/system/worker/head.js
@@ -0,0 +1,56 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// eslint-disable-next-line mozilla/no-define-cc-etc
+const Cr = {
+ NS_ERROR_NOT_IMPLEMENTED: 2147500033,
+};
+
+function add_task(func) {
+ if (!add_task.tasks) {
+ add_task.tasks = [];
+ add_task.index = 0;
+ }
+
+ add_task.tasks.push(func);
+}
+
+addEventListener("message", async function onMessage(event) {
+ function info(message) {
+ postMessage({ op: "info", message });
+ }
+
+ function executeSoon(callback) {
+ const channel = new MessageChannel();
+ channel.port1.postMessage("");
+ channel.port2.onmessage = function () {
+ callback();
+ };
+ }
+
+ function runNextTest() {
+ if (add_task.index < add_task.tasks.length) {
+ const task = add_task.tasks[add_task.index++];
+ info("add_task | Entering test " + task.name);
+ task()
+ .then(function () {
+ executeSoon(runNextTest);
+ info("add_task | Leaving test " + task.name);
+ })
+ .catch(function (ex) {
+ postMessage({ op: "failure", message: "" + ex });
+ });
+ } else {
+ postMessage({ op: "finish" });
+ }
+ }
+
+ removeEventListener("message", onMessage);
+
+ const data = event.data;
+ importScripts(...data);
+
+ executeSoon(runNextTest);
+});
diff --git a/dom/quota/test/moz.build b/dom/quota/test/moz.build
new file mode 100644
index 0000000000..ca39aaef22
--- /dev/null
+++ b/dom/quota/test/moz.build
@@ -0,0 +1,84 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+TEST_DIRS += ["gtest"]
+
+BROWSER_CHROME_MANIFESTS += ["browser/browser.ini"]
+
+MOCHITEST_MANIFESTS += ["mochitest/mochitest.ini"]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "xpcshell/caching/xpcshell.ini",
+ "xpcshell/telemetry/xpcshell.ini",
+ "xpcshell/upgrades/xpcshell.ini",
+ "xpcshell/xpcshell.ini",
+]
+
+TEST_HARNESS_FILES.testing.mochitest.browser.dom.quota.test.common += [
+ "common/browser.js",
+ "common/content.js",
+ "common/file.js",
+ "common/global.js",
+ "common/nestedtest.js",
+ "common/system.js",
+]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.dom.quota.test.common += [
+ "common/content.js",
+ "common/file.js",
+ "common/global.js",
+ "common/mochitest.js",
+ "common/test_simpledb.js",
+ "common/test_storage_manager_persist_allow.js",
+ "common/test_storage_manager_persist_deny.js",
+ "common/test_storage_manager_persisted.js",
+]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.dom.quota.test.modules += [
+ "modules/content/Assert.js",
+ "modules/content/ModuleLoader.js",
+ "modules/content/StorageUtils.js",
+ "modules/content/Utils.js",
+ "modules/content/UtilsParent.js",
+ "modules/content/WorkerDriver.js",
+]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.dom.quota.test.modules.worker += [
+ "modules/content/worker/Assert.js",
+ "modules/content/worker/head.js",
+ "modules/content/worker/ModuleLoader.js",
+ "modules/content/worker/Utils.js",
+ "modules/content/worker/UtilsChild.mjs",
+]
+
+TEST_HARNESS_FILES.xpcshell.dom.quota.test.common += [
+ "common/file.js",
+ "common/global.js",
+ "common/system.js",
+ "common/test_simpledb.js",
+ "common/xpcshell.js",
+]
+
+TEST_HARNESS_FILES.xpcshell.dom.quota.test.xpcshell.common += [
+ "xpcshell/common/head.js",
+ "xpcshell/common/utils.js",
+]
+
+TESTING_JS_MODULES.dom.quota.test.modules += [
+ "modules/system/ModuleLoader.sys.mjs",
+ "modules/system/StorageUtils.sys.mjs",
+ "modules/system/Utils.sys.mjs",
+ "modules/system/UtilsParent.sys.mjs",
+ "modules/system/WorkerDriver.sys.mjs",
+]
+
+TESTING_JS_MODULES.dom.quota.test.modules.worker += [
+ "modules/system/worker/Assert.js",
+ "modules/system/worker/head.js",
+ "modules/system/worker/ModuleLoader.js",
+ "modules/system/worker/Utils.js",
+ "modules/system/worker/UtilsChild.js",
+]
diff --git a/dom/quota/test/xpcshell/basics_profile.zip b/dom/quota/test/xpcshell/basics_profile.zip
new file mode 100644
index 0000000000..bbdb0f50cf
--- /dev/null
+++ b/dom/quota/test/xpcshell/basics_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/caching/groupMismatch_profile.zip b/dom/quota/test/xpcshell/caching/groupMismatch_profile.zip
new file mode 100644
index 0000000000..8124e589de
--- /dev/null
+++ b/dom/quota/test/xpcshell/caching/groupMismatch_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/caching/head.js b/dom/quota/test/xpcshell/caching/head.js
new file mode 100644
index 0000000000..5c36d82ca6
--- /dev/null
+++ b/dom/quota/test/xpcshell/caching/head.js
@@ -0,0 +1,14 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// The path to the top level directory.
+const depth = "../../../../../";
+
+loadScript("dom/quota/test/xpcshell/common/head.js");
+
+function loadScript(path) {
+ let uri = Services.io.newFileURI(do_get_file(depth + path));
+ Services.scriptloader.loadSubScript(uri.spec);
+}
diff --git a/dom/quota/test/xpcshell/caching/make_unsetLastAccessTime.js b/dom/quota/test/xpcshell/caching/make_unsetLastAccessTime.js
new file mode 100644
index 0000000000..9be377e4f3
--- /dev/null
+++ b/dom/quota/test/xpcshell/caching/make_unsetLastAccessTime.js
@@ -0,0 +1,25 @@
+/*
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+async function testSteps() {
+ const originDirPath = "storage/default/https+++foo.example.com";
+
+ info("Initializing");
+
+ let request = init();
+ await requestFinished(request);
+
+ info("Creating an empty origin directory");
+
+ let originDir = getRelativeFile(originDirPath);
+ originDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ // The metadata file should be now restored.
+}
diff --git a/dom/quota/test/xpcshell/caching/removedOrigin_profile.zip b/dom/quota/test/xpcshell/caching/removedOrigin_profile.zip
new file mode 100644
index 0000000000..a5ccc05aa9
--- /dev/null
+++ b/dom/quota/test/xpcshell/caching/removedOrigin_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/caching/test_groupMismatch.js b/dom/quota/test/xpcshell/caching/test_groupMismatch.js
new file mode 100644
index 0000000000..3f8e843798
--- /dev/null
+++ b/dom/quota/test/xpcshell/caching/test_groupMismatch.js
@@ -0,0 +1,45 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that the group loaded from the origin table
+ * gets updated (if necessary) before quota initialization for the given origin.
+ */
+
+async function testSteps() {
+ const principal = getPrincipal("https://foo.bar.mozilla-iot.org");
+ const originUsage = 100;
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Installing package");
+
+ // The profile contains one initialized origin directory with simple database
+ // data, a script for origin initialization and the storage database:
+ // - storage/default/https+++foo.bar.mozilla-iot.org
+ // - create_db.js
+ // - storage.sqlite
+ // The file create_db.js in the package was run locally, specifically it was
+ // temporarily added to xpcshell.ini and then executed:
+ // mach xpcshell-test --interactive dom/quota/test/xpcshell/create_db.js
+ // Note: to make it become the profile in the test, additional manual steps
+ // are needed.
+ // 1. Manually change the group and accessed values in the origin table in
+ // storage.sqlite by running this SQL statement:
+ // UPDATE origin SET group_ = 'mozilla-iot.org', accessed = 0
+ // 2. Manually change the group in .metadata-v2 from "bar.mozilla-iot.org" to
+ // "mozilla-iot.org".
+ // 3. Remove the folder "storage/temporary".
+ // 4. Remove the file "storage/ls-archive.sqlite".
+ installPackage("groupMismatch_profile");
+
+ request = getOriginUsage(principal, /* fromMemory */ true);
+ await requestFinished(request);
+
+ is(request.result.usage, originUsage, "Correct origin usage");
+}
diff --git a/dom/quota/test/xpcshell/caching/test_removedOrigin.js b/dom/quota/test/xpcshell/caching/test_removedOrigin.js
new file mode 100644
index 0000000000..8b5702ad9c
--- /dev/null
+++ b/dom/quota/test/xpcshell/caching/test_removedOrigin.js
@@ -0,0 +1,61 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Verify that temporary storage initialization will notice a removed origin
+ * that the cache has data for and which indicates the origin was accessed
+ * during the last run. Currently, we expect LoadQuotaFromCache to fail because
+ * of this inconsistency and to fall back to full initialization.
+ */
+
+async function testSteps() {
+ const principal = getPrincipal("http://example.com");
+ const originUsage = 0;
+
+ info("Setting pref");
+
+ // The packaged profile will have a different build ID and we would treat the
+ // cache as invalid if we didn't bypass this check.
+ Services.prefs.setBoolPref("dom.quotaManager.caching.checkBuildId", false);
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Installing package");
+
+ // The profile contains empty default storage, a script for origin
+ // initialization and the storage database:
+ // - storage/default
+ // - create_db.js
+ // - storage.sqlite
+ // The file create_db.js in the package was run locally, specifically it was
+ // temporarily added to xpcshell.ini and then executed:
+ // mach xpcshell-test --interactive dom/quota/test/xpcshell/create_db.js
+ // Note: to make it become the profile in the test, additional manual steps
+ // are needed.
+ // 1. Remove the folder "storage/default/http+++example.com".
+ // 2. Remove the folder "storage/temporary".
+ // 3. Remove the file "storage/ls-archive.sqlite".
+ installPackage("removedOrigin_profile");
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Getting origin usage");
+
+ request = getOriginUsage(principal, /* fromMemory */ true);
+ await requestFinished(request);
+
+ is(request.result.usage, originUsage, "Correct origin usage");
+}
diff --git a/dom/quota/test/xpcshell/caching/test_unsetLastAccessTime.js b/dom/quota/test/xpcshell/caching/test_unsetLastAccessTime.js
new file mode 100644
index 0000000000..5abe76eade
--- /dev/null
+++ b/dom/quota/test/xpcshell/caching/test_unsetLastAccessTime.js
@@ -0,0 +1,46 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+ const principal = getPrincipal("https://foo.example.com/");
+
+ info("Setting pref");
+
+ // The packaged profile will have a different build ID and we would treat the
+ // cache as invalid if we didn't bypass this check.
+ Services.prefs.setBoolPref("dom.quotaManager.caching.checkBuildId", false);
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Installing package");
+
+ // The profile contains one initialized origin directory and the storage
+ // database:
+ // - storage/default/https+++foo.example.com
+ // - storage.sqlite
+ // The file make_unsetLastAccessTime.js was run locally, specifically it was
+ // temporarily enabled in xpcshell.ini and then executed:
+ // mach test --interactive dom/quota/test/xpcshell/caching/make_unsetLastAccessTime.js
+ // Note: to make it become the profile in the test, additional manual steps
+ // are needed.
+ // 1. Remove the folder "storage/temporary".
+ // 2. Remove the file "storage/ls-archive.sqlite".
+ installPackage("unsetLastAccessTime_profile");
+
+ info("Getting full origin metadata");
+
+ request = getFullOriginMetadata("default", principal);
+ await requestFinished(request);
+
+ info("Verifying last access time");
+
+ ok(
+ BigInt(request.result.lastAccessTime) != INT64_MIN,
+ "Correct last access time"
+ );
+}
diff --git a/dom/quota/test/xpcshell/caching/unsetLastAccessTime_profile.zip b/dom/quota/test/xpcshell/caching/unsetLastAccessTime_profile.zip
new file mode 100644
index 0000000000..2b14ca7276
--- /dev/null
+++ b/dom/quota/test/xpcshell/caching/unsetLastAccessTime_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/caching/xpcshell.ini b/dom/quota/test/xpcshell/caching/xpcshell.ini
new file mode 100644
index 0000000000..d08f15c0a8
--- /dev/null
+++ b/dom/quota/test/xpcshell/caching/xpcshell.ini
@@ -0,0 +1,17 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+head = head.js
+support-files =
+ groupMismatch_profile.zip
+ removedOrigin_profile.zip
+
+[make_unsetLastAccessTime.js]
+skip-if = true # Only used for recreating unsetLastAccessTime_profile.zip
+[test_groupMismatch.js]
+[test_removedOrigin.js]
+[test_unsetLastAccessTime.js]
+support-files =
+ unsetLastAccessTime_profile.zip
diff --git a/dom/quota/test/xpcshell/clearStoragesForPrincipal_profile.zip b/dom/quota/test/xpcshell/clearStoragesForPrincipal_profile.zip
new file mode 100644
index 0000000000..7d7985ddd0
--- /dev/null
+++ b/dom/quota/test/xpcshell/clearStoragesForPrincipal_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/clearStoragesForPrivateBrowsing_profile.json b/dom/quota/test/xpcshell/clearStoragesForPrivateBrowsing_profile.json
new file mode 100644
index 0000000000..b4b8e3afda
--- /dev/null
+++ b/dom/quota/test/xpcshell/clearStoragesForPrivateBrowsing_profile.json
@@ -0,0 +1,152 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++example.com",
+ "dir": true,
+ "entries": [
+ { "name": "cache", "dir": true },
+ { "name": "fs", "dir": true },
+ { "name": "idb", "dir": true },
+ { "name": "ls", "dir": true },
+ { "name": "sdb", "dir": true },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++example.com",
+ "dir": true,
+ "entries": [
+ { "name": "cache", "dir": true },
+ { "name": "fs", "dir": true },
+ { "name": "idb", "dir": true },
+ { "name": "ls", "dir": true },
+ { "name": "sdb", "dir": true },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "private",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++example.com^privateBrowsingId=1",
+ "dir": true,
+ "entries": [
+ { "name": "cache", "dir": true },
+ { "name": "fs", "dir": true },
+ { "name": "idb", "dir": true },
+ { "name": "ls", "dir": true },
+ { "name": "sdb", "dir": true }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++example.com",
+ "dir": true,
+ "entries": [
+ { "name": "cache", "dir": true },
+ { "name": "fs", "dir": true },
+ { "name": "idb", "dir": true },
+ { "name": "ls", "dir": true },
+ { "name": "sdb", "dir": true },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ },
+ {
+ "key": "afterClearPrivateBrowsing",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++example.com",
+ "dir": true,
+ "entries": [
+ { "name": "cache", "dir": true },
+ { "name": "fs", "dir": true },
+ { "name": "idb", "dir": true },
+ { "name": "ls", "dir": true },
+ { "name": "sdb", "dir": true },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++example.com",
+ "dir": true,
+ "entries": [
+ { "name": "cache", "dir": true },
+ { "name": "fs", "dir": true },
+ { "name": "idb", "dir": true },
+ { "name": "ls", "dir": true },
+ { "name": "sdb", "dir": true },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++example.com",
+ "dir": true,
+ "entries": [
+ { "name": "cache", "dir": true },
+ { "name": "fs", "dir": true },
+ { "name": "idb", "dir": true },
+ { "name": "ls", "dir": true },
+ { "name": "sdb", "dir": true },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/clearStoragesForPrivateBrowsing_profile.zip b/dom/quota/test/xpcshell/clearStoragesForPrivateBrowsing_profile.zip
new file mode 100644
index 0000000000..4bbb8a5c77
--- /dev/null
+++ b/dom/quota/test/xpcshell/clearStoragesForPrivateBrowsing_profile.zip
Binary files differ
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);
+}
diff --git a/dom/quota/test/xpcshell/createLocalStorage_profile.zip b/dom/quota/test/xpcshell/createLocalStorage_profile.zip
new file mode 100644
index 0000000000..d5958dbd59
--- /dev/null
+++ b/dom/quota/test/xpcshell/createLocalStorage_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/defaultStorageDirectory_shared.json b/dom/quota/test/xpcshell/defaultStorageDirectory_shared.json
new file mode 100644
index 0000000000..7fff48475b
--- /dev/null
+++ b/dom/quota/test/xpcshell/defaultStorageDirectory_shared.json
@@ -0,0 +1,141 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ },
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ },
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInitTemporaryStorage",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ { "name": "ls-archive.sqlite", "dir": false },
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ },
+ {
+ "name": "default",
+ "dir": true,
+ "todo": "Add entry invalid+++example.com once bug 1594075 is fixed",
+ "entries": [{ "name": "foo.bar", "dir": false }]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "todo": "Add entry invalid+++example.com once bug 1594075 is fixed",
+ "entries": [{ "name": "foo.bar", "dir": false }]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterClearPrivateBrowsing",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ { "name": "ls-archive.sqlite", "dir": false },
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ },
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/defaultStorageDirectory_shared.zip b/dom/quota/test/xpcshell/defaultStorageDirectory_shared.zip
new file mode 100644
index 0000000000..61e5b60a87
--- /dev/null
+++ b/dom/quota/test/xpcshell/defaultStorageDirectory_shared.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/getUsage_profile.zip b/dom/quota/test/xpcshell/getUsage_profile.zip
new file mode 100644
index 0000000000..5144112bde
--- /dev/null
+++ b/dom/quota/test/xpcshell/getUsage_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/groupMismatch_profile.zip b/dom/quota/test/xpcshell/groupMismatch_profile.zip
new file mode 100644
index 0000000000..182b013de0
--- /dev/null
+++ b/dom/quota/test/xpcshell/groupMismatch_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/head.js b/dom/quota/test/xpcshell/head.js
new file mode 100644
index 0000000000..bf9ba22ce3
--- /dev/null
+++ b/dom/quota/test/xpcshell/head.js
@@ -0,0 +1,14 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// The path to the top level directory.
+const depth = "../../../../";
+
+loadScript("dom/quota/test/xpcshell/common/head.js");
+
+function loadScript(path) {
+ let uri = Services.io.newFileURI(do_get_file(depth + path));
+ Services.scriptloader.loadSubScript(uri.spec);
+}
diff --git a/dom/quota/test/xpcshell/indexedDBDirectory_shared.json b/dom/quota/test/xpcshell/indexedDBDirectory_shared.json
new file mode 100644
index 0000000000..6e3d63bafc
--- /dev/null
+++ b/dom/quota/test/xpcshell/indexedDBDirectory_shared.json
@@ -0,0 +1,35 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "indexedDB",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/indexedDBDirectory_shared.zip b/dom/quota/test/xpcshell/indexedDBDirectory_shared.zip
new file mode 100644
index 0000000000..6b959e7525
--- /dev/null
+++ b/dom/quota/test/xpcshell/indexedDBDirectory_shared.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/make_unknownFiles.js b/dom/quota/test/xpcshell/make_unknownFiles.js
new file mode 100644
index 0000000000..5e84c623c1
--- /dev/null
+++ b/dom/quota/test/xpcshell/make_unknownFiles.js
@@ -0,0 +1,172 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+loadScript("dom/quota/test/common/file.js");
+
+async function testSteps() {
+ const principal = getPrincipal("http://example.com");
+
+ const repoRelativePath = "storage/default";
+ const originRelativePath = `${repoRelativePath}/http+++example.com`;
+
+ let unknownFileCounter = 1;
+ let unknownDirCounter = 1;
+
+ function createUnknownFileIn(dirRelativePath, recursive) {
+ const dir = getRelativeFile(dirRelativePath);
+
+ let file = dir.clone();
+ file.append("foo" + unknownFileCounter + ".bar");
+
+ const ostream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+
+ ostream.init(file, -1, parseInt("0644", 8), 0);
+
+ ostream.write("x".repeat(unknownFileCounter), unknownFileCounter);
+
+ ostream.close();
+
+ unknownFileCounter++;
+
+ if (recursive) {
+ const entries = dir.directoryEntries;
+ while ((file = entries.nextFile)) {
+ if (file.isDirectory()) {
+ createUnknownFileIn(dirRelativePath + "/" + file.leafName);
+ }
+ }
+ }
+ }
+
+ function createUnknownDirectoryIn(dirRelativePath) {
+ createUnknownFileIn(dirRelativePath + "/foo" + unknownDirCounter++);
+ }
+
+ // storage.sqlite and storage/ls-archive.sqlite
+ {
+ const request = init();
+ await requestFinished(request);
+ }
+
+ // Unknown file in the repository
+ {
+ createUnknownFileIn(repoRelativePath);
+ }
+
+ // Unknown file and unknown directory in the origin directory
+ {
+ let request = init();
+ await requestFinished(request);
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ request = initTemporaryOrigin("default", principal);
+ await requestFinished(request);
+
+ ok(request.result === true, "The origin directory was created");
+
+ createUnknownFileIn(originRelativePath);
+ createUnknownDirectoryIn(originRelativePath);
+ }
+
+ // Unknown files in idb client directory and its subdirectories and unknown
+ // directory in .files directory
+ {
+ const request = indexedDB.openForPrincipal(principal, "myIndexedDB");
+ await openDBRequestUpgradeNeeded(request);
+
+ const database = request.result;
+
+ const objectStore = database.createObjectStore("Blobs", {});
+
+ objectStore.add(getNullBlob(200), 42);
+
+ await openDBRequestSucceeded(request);
+
+ database.close();
+
+ createUnknownFileIn(`${originRelativePath}/idb`);
+ createUnknownFileIn(
+ `${originRelativePath}/idb/2320029346mByDIdnedxe.files`
+ );
+ createUnknownDirectoryIn(
+ `${originRelativePath}/idb/2320029346mByDIdnedxe.files`
+ );
+ createUnknownFileIn(
+ `${originRelativePath}/idb/2320029346mByDIdnedxe.files/journals`
+ );
+ }
+
+ // Unknown files in cache client directory and its subdirectories
+ {
+ async function sandboxScript() {
+ const cache = await caches.open("myCache");
+ const request = new Request("http://example.com/index.html");
+ const response = new Response("hello world");
+ await cache.put(request, response);
+ }
+
+ const sandbox = new Cu.Sandbox(principal, {
+ wantGlobalProperties: ["caches", "fetch"],
+ });
+
+ const promise = new Promise(function (resolve, reject) {
+ sandbox.resolve = resolve;
+ sandbox.reject = reject;
+ });
+
+ Cu.evalInSandbox(
+ sandboxScript.toSource() + " sandboxScript().then(resolve, reject);",
+ sandbox
+ );
+ await promise;
+
+ createUnknownFileIn(`${originRelativePath}/cache`);
+ createUnknownFileIn(
+ `${originRelativePath}/cache/morgue`,
+ /* recursive */ true
+ );
+ }
+
+ // Unknown file and unknown directory in sdb client directory
+ {
+ const database = getSimpleDatabase(principal);
+
+ let request = database.open("mySimpleDB");
+ await requestFinished(request);
+
+ request = database.write(getBuffer(100));
+ await requestFinished(request);
+
+ request = database.close();
+ await requestFinished(request);
+
+ createUnknownFileIn(`${originRelativePath}/sdb`);
+ createUnknownDirectoryIn(`${originRelativePath}/sdb`);
+ }
+
+ // Unknown file and unknown directory in ls client directory
+ {
+ Services.prefs.setBoolPref("dom.storage.testing", true);
+ Services.prefs.setBoolPref("dom.storage.client_validation", false);
+
+ const storage = Services.domStorageManager.createStorage(
+ null,
+ principal,
+ principal,
+ ""
+ );
+
+ storage.setItem("foo", "bar");
+
+ storage.close();
+
+ createUnknownFileIn(`${originRelativePath}/ls`);
+ createUnknownDirectoryIn(`${originRelativePath}/ls`);
+ }
+}
diff --git a/dom/quota/test/xpcshell/make_unsetLastAccessTime.js b/dom/quota/test/xpcshell/make_unsetLastAccessTime.js
new file mode 100644
index 0000000000..9be377e4f3
--- /dev/null
+++ b/dom/quota/test/xpcshell/make_unsetLastAccessTime.js
@@ -0,0 +1,25 @@
+/*
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+async function testSteps() {
+ const originDirPath = "storage/default/https+++foo.example.com";
+
+ info("Initializing");
+
+ let request = init();
+ await requestFinished(request);
+
+ info("Creating an empty origin directory");
+
+ let originDir = getRelativeFile(originDirPath);
+ originDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ // The metadata file should be now restored.
+}
diff --git a/dom/quota/test/xpcshell/originMismatch_profile.json b/dom/quota/test/xpcshell/originMismatch_profile.json
new file mode 100644
index 0000000000..cbeaef728a
--- /dev/null
+++ b/dom/quota/test/xpcshell/originMismatch_profile.json
@@ -0,0 +1,77 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.example.com",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata-v2", "dir": false },
+ {
+ "name": "cache",
+ "dir": true,
+ "entries": [{ "name": ".padding", "dir": false }]
+ }
+ ]
+ },
+ { "name": "http+++www.example.com.", "dir": true },
+ {
+ "name": "http+++www.example.org",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata-v2", "dir": false },
+ {
+ "name": "cache",
+ "dir": true,
+ "entries": [{ "name": ".padding", "dir": false }]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ },
+ {
+ "key": "afterInitTemporaryStorage",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.example.com.",
+ "dir": true,
+ "entries": [{ "name": ".metadata-v2", "dir": false }]
+ },
+ {
+ "name": "http+++www.example.org.",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata-v2", "dir": false },
+ { "name": "cache", "dir": true }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/originMismatch_profile.zip b/dom/quota/test/xpcshell/originMismatch_profile.zip
new file mode 100644
index 0000000000..dd5795736f
--- /dev/null
+++ b/dom/quota/test/xpcshell/originMismatch_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/persistentStorageDirectory_shared.json b/dom/quota/test/xpcshell/persistentStorageDirectory_shared.json
new file mode 100644
index 0000000000..8d36293bbf
--- /dev/null
+++ b/dom/quota/test/xpcshell/persistentStorageDirectory_shared.json
@@ -0,0 +1,57 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "persistent",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ { "name": "invalid+++example.com", "dir": true },
+ { "name": "foo.bar", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/persistentStorageDirectory_shared.zip b/dom/quota/test/xpcshell/persistentStorageDirectory_shared.zip
new file mode 100644
index 0000000000..b80bfe904b
--- /dev/null
+++ b/dom/quota/test/xpcshell/persistentStorageDirectory_shared.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/removeLocalStorage1_profile.zip b/dom/quota/test/xpcshell/removeLocalStorage1_profile.zip
new file mode 100644
index 0000000000..19e971433c
--- /dev/null
+++ b/dom/quota/test/xpcshell/removeLocalStorage1_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/removeLocalStorage2_profile.zip b/dom/quota/test/xpcshell/removeLocalStorage2_profile.zip
new file mode 100644
index 0000000000..04d1a3462b
--- /dev/null
+++ b/dom/quota/test/xpcshell/removeLocalStorage2_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/head.js b/dom/quota/test/xpcshell/telemetry/head.js
new file mode 100644
index 0000000000..5c36d82ca6
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/head.js
@@ -0,0 +1,14 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// The path to the top level directory.
+const depth = "../../../../../";
+
+loadScript("dom/quota/test/xpcshell/common/head.js");
+
+function loadScript(path) {
+ let uri = Services.io.newFileURI(do_get_file(depth + path));
+ Services.scriptloader.loadSubScript(uri.spec);
+}
diff --git a/dom/quota/test/xpcshell/telemetry/test_qm_first_initialization_attempt.js b/dom/quota/test/xpcshell/telemetry/test_qm_first_initialization_attempt.js
new file mode 100644
index 0000000000..9a3afba8c5
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/test_qm_first_initialization_attempt.js
@@ -0,0 +1,866 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const storageDirName = "storage";
+const storageFileName = "storage.sqlite";
+const indexedDBDirName = "indexedDB";
+const persistentStorageDirName = "storage/persistent";
+const histogramName = "QM_FIRST_INITIALIZATION_ATTEMPT";
+
+const testcases = [
+ {
+ mainKey: "Storage",
+ async setup(expectedInitResult) {
+ if (!expectedInitResult) {
+ // Make the database unusable by creating it as a directory (not a
+ // file).
+ const storageFile = getRelativeFile(storageFileName);
+ storageFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+ },
+ initFunction: init,
+ expectedSnapshots: {
+ initFailure: {
+ // mainKey
+ Storage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ // mainKey
+ Storage: {
+ values: [1, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ mainKey: "TemporaryStorage",
+ async setup(expectedInitResult) {
+ // We need to initialize storage before populating the repositories. If
+ // we don't do that, the storage directory created for the repositories
+ // would trigger storage upgrades (from version 0 to current version).
+ let request = init();
+ await requestFinished(request);
+
+ populateRepository("temporary");
+ populateRepository("default");
+
+ if (!expectedInitResult) {
+ makeRepositoryUnusable("temporary");
+ makeRepositoryUnusable("default");
+ }
+ },
+ initFunction: initTemporaryStorage,
+ getExpectedSnapshots() {
+ const expectedSnapshotsInNightly = {
+ initFailure: {
+ Storage: {
+ values: [0, 1, 0],
+ },
+ TemporaryRepository: {
+ values: [1, 0],
+ },
+ DefaultRepository: {
+ values: [1, 0],
+ },
+ // mainKey
+ TemporaryStorage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ Storage: {
+ values: [0, 2, 0],
+ },
+ TemporaryRepository: {
+ values: [1, 1, 0],
+ },
+ DefaultRepository: {
+ values: [1, 1, 0],
+ },
+ // mainKey
+ TemporaryStorage: {
+ values: [1, 1, 0],
+ },
+ },
+ };
+
+ const expectedSnapshotsInOthers = {
+ initFailure: {
+ Storage: {
+ values: [0, 1, 0],
+ },
+ TemporaryRepository: {
+ values: [1, 0],
+ },
+ // mainKey
+ TemporaryStorage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ Storage: {
+ values: [0, 2, 0],
+ },
+ TemporaryRepository: {
+ values: [1, 1, 0],
+ },
+ DefaultRepository: {
+ values: [0, 1, 0],
+ },
+ // mainKey
+ TemporaryStorage: {
+ values: [1, 1, 0],
+ },
+ },
+ };
+
+ return AppConstants.NIGHTLY_BUILD
+ ? expectedSnapshotsInNightly
+ : expectedSnapshotsInOthers;
+ },
+ },
+ {
+ mainKey: "DefaultRepository",
+ async setup(expectedInitResult) {
+ // See the comment for "TemporaryStorage".
+ let request = init();
+ await requestFinished(request);
+
+ populateRepository("default");
+
+ if (!expectedInitResult) {
+ makeRepositoryUnusable("default");
+ }
+ },
+ initFunction: initTemporaryStorage,
+ expectedSnapshots: {
+ initFailure: {
+ Storage: {
+ values: [0, 1, 0],
+ },
+ TemporaryRepository: {
+ values: [0, 1, 0],
+ },
+ // mainKey
+ DefaultRepository: {
+ values: [1, 0],
+ },
+ TemporaryStorage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ Storage: {
+ values: [0, 2, 0],
+ },
+ TemporaryRepository: {
+ values: [0, 2, 0],
+ },
+ // mainKey
+ DefaultRepository: {
+ values: [1, 1, 0],
+ },
+ TemporaryStorage: {
+ values: [1, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ mainKey: "TemporaryRepository",
+ async setup(expectedInitResult) {
+ // See the comment for "TemporaryStorage".
+ let request = init();
+ await requestFinished(request);
+
+ populateRepository("temporary");
+
+ if (!expectedInitResult) {
+ makeRepositoryUnusable("temporary");
+ }
+ },
+ initFunction: initTemporaryStorage,
+ getExpectedSnapshots() {
+ const expectedSnapshotsInNightly = {
+ initFailure: {
+ Storage: {
+ values: [0, 1, 0],
+ },
+ // mainKey
+ TemporaryRepository: {
+ values: [1, 0],
+ },
+ DefaultRepository: {
+ values: [0, 1, 0],
+ },
+ TemporaryStorage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ Storage: {
+ values: [0, 2, 0],
+ },
+ // mainKey
+ TemporaryRepository: {
+ values: [1, 1, 0],
+ },
+ DefaultRepository: {
+ values: [0, 2, 0],
+ },
+ TemporaryStorage: {
+ values: [1, 1, 0],
+ },
+ },
+ };
+
+ const expectedSnapshotsInOthers = {
+ initFailure: {
+ Storage: {
+ values: [0, 1, 0],
+ },
+ // mainKey
+ TemporaryRepository: {
+ values: [1, 0],
+ },
+ TemporaryStorage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ Storage: {
+ values: [0, 2, 0],
+ },
+ // mainKey
+ TemporaryRepository: {
+ values: [1, 1, 0],
+ },
+ DefaultRepository: {
+ values: [0, 1, 0],
+ },
+ TemporaryStorage: {
+ values: [1, 1, 0],
+ },
+ },
+ };
+
+ return AppConstants.NIGHTLY_BUILD
+ ? expectedSnapshotsInNightly
+ : expectedSnapshotsInOthers;
+ },
+ },
+ {
+ mainKey: "UpgradeStorageFrom0_0To1_0",
+ async setup(expectedInitResult) {
+ // storage used prior FF 49 (storage version 0.0)
+ installPackage("version0_0_profile");
+
+ if (!expectedInitResult) {
+ installPackage("version0_0_make_it_unusable");
+ }
+ },
+ initFunction: init,
+ expectedSnapshots: {
+ initFailure: {
+ // mainKey
+ UpgradeStorageFrom0_0To1_0: {
+ values: [1, 0],
+ },
+ Storage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ // mainKey
+ UpgradeStorageFrom0_0To1_0: {
+ values: [1, 1, 0],
+ },
+ UpgradeStorageFrom1_0To2_0: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_0To2_1: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_1To2_2: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_2To2_3: {
+ values: [0, 1, 0],
+ },
+ Storage: {
+ values: [1, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ mainKey: "UpgradeStorageFrom1_0To2_0",
+ async setup(expectedInitResult) {
+ // storage used by FF 49-54 (storage version 1.0)
+ installPackage("version1_0_profile");
+
+ if (!expectedInitResult) {
+ installPackage("version1_0_make_it_unusable");
+ }
+ },
+ initFunction: init,
+ expectedSnapshots: {
+ initFailure: {
+ // mainKey
+ UpgradeStorageFrom1_0To2_0: {
+ values: [1, 0],
+ },
+ Storage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ // mainKey
+ UpgradeStorageFrom1_0To2_0: {
+ values: [1, 1, 0],
+ },
+ UpgradeStorageFrom2_0To2_1: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_1To2_2: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_2To2_3: {
+ values: [0, 1, 0],
+ },
+ Storage: {
+ values: [1, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ mainKey: "UpgradeStorageFrom2_0To2_1",
+ async setup(expectedInitResult) {
+ // storage used by FF 55-56 (storage version 2.0)
+ installPackage("version2_0_profile");
+
+ if (!expectedInitResult) {
+ installPackage("version2_0_make_it_unusable");
+ }
+ },
+ initFunction: init,
+ expectedSnapshots: {
+ initFailure: {
+ // mainKey
+ UpgradeStorageFrom2_0To2_1: {
+ values: [1, 0],
+ },
+ Storage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ // mainKey
+ UpgradeStorageFrom2_0To2_1: {
+ values: [1, 1, 0],
+ },
+ UpgradeStorageFrom2_1To2_2: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_2To2_3: {
+ values: [0, 1, 0],
+ },
+ Storage: {
+ values: [1, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ mainKey: "UpgradeStorageFrom2_1To2_2",
+ async setup(expectedInitResult) {
+ // storage used by FF 57-67 (storage version 2.1)
+ installPackage("version2_1_profile");
+
+ if (!expectedInitResult) {
+ installPackage("version2_1_make_it_unusable");
+ }
+ },
+ initFunction: init,
+ expectedSnapshots: {
+ initFailure: {
+ // mainKey
+ UpgradeStorageFrom2_1To2_2: {
+ values: [1, 0],
+ },
+ Storage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ // mainKey
+ UpgradeStorageFrom2_1To2_2: {
+ values: [1, 1, 0],
+ },
+ UpgradeStorageFrom2_2To2_3: {
+ values: [0, 1, 0],
+ },
+ Storage: {
+ values: [1, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ mainKey: "UpgradeStorageFrom2_2To2_3",
+ async setup(expectedInitResult) {
+ // storage used by FF 68-69 (storage version 2.2)
+ installPackage("version2_2_profile");
+
+ if (!expectedInitResult) {
+ installPackage(
+ "version2_2_make_it_unusable",
+ /* allowFileOverwrites */ true
+ );
+ }
+ },
+ initFunction: init,
+ expectedSnapshots: {
+ initFailure: {
+ // mainKey
+ UpgradeStorageFrom2_2To2_3: {
+ values: [1, 0],
+ },
+ Storage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ // mainKey
+ UpgradeStorageFrom2_2To2_3: {
+ values: [1, 1, 0],
+ },
+ Storage: {
+ values: [1, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ mainKey: "UpgradeFromIndexedDBDirectory",
+ async setup(expectedInitResult) {
+ const indexedDBDir = getRelativeFile(indexedDBDirName);
+ indexedDBDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+
+ if (!expectedInitResult) {
+ // "indexedDB" directory will be moved under "storage" directory and at
+ // the same time renamed to "persistent". Create a storage file to cause
+ // the moves to fail.
+ const storageFile = getRelativeFile(storageDirName);
+ storageFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+ }
+ },
+ initFunction: init,
+ expectedSnapshots: {
+ initFailure: {
+ // mainKey
+ UpgradeFromIndexedDBDirectory: {
+ values: [1, 0],
+ },
+ Storage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ // mainKey
+ UpgradeFromIndexedDBDirectory: {
+ values: [1, 1, 0],
+ },
+ UpgradeFromPersistentStorageDirectory: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom0_0To1_0: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom1_0To2_0: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_0To2_1: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_1To2_2: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_2To2_3: {
+ values: [0, 1, 0],
+ },
+ Storage: {
+ values: [1, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ mainKey: "UpgradeFromPersistentStorageDirectory",
+ async setup(expectedInitResult) {
+ const persistentStorageDir = getRelativeFile(persistentStorageDirName);
+ persistentStorageDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+
+ if (!expectedInitResult) {
+ // Create a metadata directory to break creating or upgrading directory
+ // metadata files.
+ const metadataDir = getRelativeFile(
+ "storage/persistent/https+++bad.example.com/.metadata"
+ );
+ metadataDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+ },
+ initFunction: init,
+ expectedSnapshots: {
+ initFailure: {
+ // mainKey
+ UpgradeFromPersistentStorageDirectory: {
+ values: [1, 0],
+ },
+ Storage: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ // mainKey
+ UpgradeFromPersistentStorageDirectory: {
+ values: [1, 1, 0],
+ },
+ UpgradeStorageFrom0_0To1_0: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom1_0To2_0: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_0To2_1: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_1To2_2: {
+ values: [0, 1, 0],
+ },
+ UpgradeStorageFrom2_2To2_3: {
+ values: [0, 1, 0],
+ },
+ Storage: {
+ values: [1, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ mainKey: "PersistentOrigin",
+ async setup(expectedInitResult) {
+ // We need to initialize storage before creating the origin files. If we
+ // don't do that, the storage directory created for the origin files
+ // would trigger storage upgrades (from version 0 to current version).
+ let request = init();
+ await requestFinished(request);
+
+ if (!expectedInitResult) {
+ const originFiles = [
+ getRelativeFile("storage/permanent/https+++example.com"),
+ getRelativeFile("storage/permanent/https+++example1.com"),
+ getRelativeFile("storage/default/https+++example2.com"),
+ ];
+
+ for (const originFile of originFiles) {
+ originFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+ }
+ }
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+ },
+ initFunctions: [
+ {
+ name: initPersistentOrigin,
+ args: [getPrincipal("https://example.com")],
+ },
+ {
+ name: initPersistentOrigin,
+ args: [getPrincipal("https://example1.com")],
+ },
+ {
+ name: initTemporaryOrigin,
+ args: ["default", getPrincipal("https://example2.com")],
+ },
+ ],
+ expectedSnapshots: {
+ initFailure: {
+ Storage: {
+ values: [0, 1, 0],
+ },
+ TemporaryRepository: {
+ values: [0, 1, 0],
+ },
+ DefaultRepository: {
+ values: [0, 1, 0],
+ },
+ TemporaryStorage: {
+ values: [0, 1, 0],
+ },
+ // mainKey
+ PersistentOrigin: {
+ values: [2, 0],
+ },
+ TemporaryOrigin: {
+ values: [1, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ Storage: {
+ values: [0, 2, 0],
+ },
+ TemporaryRepository: {
+ values: [0, 2, 0],
+ },
+ DefaultRepository: {
+ values: [0, 2, 0],
+ },
+ TemporaryStorage: {
+ values: [0, 2, 0],
+ },
+ // mainKey
+ PersistentOrigin: {
+ values: [2, 2, 0],
+ },
+ TemporaryOrigin: {
+ values: [1, 1, 0],
+ },
+ },
+ },
+ },
+ {
+ mainKey: "TemporaryOrigin",
+ async setup(expectedInitResult) {
+ // See the comment for "PersistentOrigin".
+ let request = init();
+ await requestFinished(request);
+
+ if (!expectedInitResult) {
+ const originFiles = [
+ getRelativeFile("storage/temporary/https+++example.com"),
+ getRelativeFile("storage/default/https+++example.com"),
+ getRelativeFile("storage/default/https+++example1.com"),
+ getRelativeFile("storage/permanent/https+++example2.com"),
+ ];
+
+ for (const originFile of originFiles) {
+ originFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+ }
+ }
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+ },
+ initFunctions: [
+ {
+ name: initTemporaryOrigin,
+ args: ["temporary", getPrincipal("https://example.com")],
+ },
+ {
+ name: initTemporaryOrigin,
+ args: ["default", getPrincipal("https://example.com")],
+ },
+ {
+ name: initTemporaryOrigin,
+ args: ["default", getPrincipal("https://example1.com")],
+ },
+ {
+ name: initPersistentOrigin,
+ args: [getPrincipal("https://example2.com")],
+ },
+ ],
+ // Only the first result of EnsureTemporaryOriginIsInitialized per origin
+ // should be reported. Thus, only the results for (temporary, example.com),
+ // and (default, example1.com) should be reported.
+ expectedSnapshots: {
+ initFailure: {
+ Storage: {
+ values: [0, 1, 0],
+ },
+ TemporaryRepository: {
+ values: [0, 1, 0],
+ },
+ DefaultRepository: {
+ values: [0, 1, 0],
+ },
+ TemporaryStorage: {
+ values: [0, 1, 0],
+ },
+ PersistentOrigin: {
+ values: [1, 0],
+ },
+ // mainKey
+ TemporaryOrigin: {
+ values: [2, 0],
+ },
+ },
+ initFailureThenSuccess: {
+ Storage: {
+ values: [0, 2, 0],
+ },
+ TemporaryRepository: {
+ values: [0, 2, 0],
+ },
+ DefaultRepository: {
+ values: [0, 2, 0],
+ },
+ TemporaryStorage: {
+ values: [0, 2, 0],
+ },
+ PersistentOrigin: {
+ values: [1, 1, 0],
+ },
+ // mainKey
+ TemporaryOrigin: {
+ values: [2, 2, 0],
+ },
+ },
+ },
+ },
+];
+
+loadScript("dom/quota/test/xpcshell/common/utils.js");
+
+function verifyHistogram(histogram, mainKey, expectedSnapshot) {
+ const snapshot = histogram.snapshot();
+
+ ok(
+ mainKey in snapshot,
+ `The histogram ${histogram.name()} must contain the main key ${mainKey}`
+ );
+
+ const keys = Object.keys(snapshot);
+
+ is(
+ keys.length,
+ Object.keys(expectedSnapshot).length,
+ `The number of keys must match the expected number of keys for ` +
+ `${histogram.name()}`
+ );
+
+ for (const key of keys) {
+ ok(
+ key in expectedSnapshot,
+ `The key ${key} must match the expected keys for ${histogram.name()}`
+ );
+
+ const values = Object.entries(snapshot[key].values);
+ const expectedValues = expectedSnapshot[key].values;
+
+ is(
+ values.length,
+ expectedValues.length,
+ `The number of values should match the expected number of values for ` +
+ `${histogram.name()}`
+ );
+
+ for (let [i, val] of values) {
+ is(
+ val,
+ expectedValues[i],
+ `Expected counts should match for ${histogram.name()} at index ${i}`
+ );
+ }
+ }
+}
+
+async function testSteps() {
+ let request;
+ for (const testcase of testcases) {
+ const mainKey = testcase.mainKey;
+
+ info(`Verifying ${histogramName} histogram for the main key ${mainKey}`);
+
+ const histogram =
+ TelemetryTestUtils.getAndClearKeyedHistogram(histogramName);
+
+ for (const expectedInitResult of [false, true]) {
+ info(
+ `Verifying the histogram when the initialization ` +
+ `${expectedInitResult ? "failed and then succeeds" : "fails"}`
+ );
+
+ await testcase.setup(expectedInitResult);
+
+ const msg = `Should ${expectedInitResult ? "not " : ""} have thrown`;
+
+ // Call the initialization function twice, so we can verify below that
+ // only the first initialization attempt has been reported.
+ for (let i = 0; i < 2; ++i) {
+ let initFunctions;
+
+ if (testcase.initFunctions) {
+ initFunctions = testcase.initFunctions;
+ } else {
+ initFunctions = [
+ {
+ name: testcase.initFunction,
+ args: [],
+ },
+ ];
+ }
+
+ for (const initFunction of initFunctions) {
+ request = initFunction.name(...initFunction.args);
+ try {
+ await requestFinished(request);
+ ok(expectedInitResult, msg);
+ } catch (ex) {
+ ok(!expectedInitResult, msg);
+ }
+ }
+ }
+
+ const expectedSnapshots = testcase.getExpectedSnapshots
+ ? testcase.getExpectedSnapshots()
+ : testcase.expectedSnapshots;
+
+ const expectedSnapshot = expectedInitResult
+ ? expectedSnapshots.initFailureThenSuccess
+ : expectedSnapshots.initFailure;
+
+ verifyHistogram(histogram, mainKey, expectedSnapshot);
+
+ // The first initialization attempt has been reported in the histogram
+ // and any new attemps wouldn't be reported if we didn't reset or clear
+ // the storage here. We need a clean profile for the next iteration
+ // anyway.
+ // However, the clear storage operation needs initialized storage, so
+ // clearing can fail if the storage is unusable and it can also increase
+ // some of the telemetry counters. Instead of calling clear, we can just
+ // call reset and clear profile manually.
+ request = reset();
+ await requestFinished(request);
+
+ const indexedDBDir = getRelativeFile(indexedDBDirName);
+ if (indexedDBDir.exists()) {
+ indexedDBDir.remove(false);
+ }
+
+ const storageDir = getRelativeFile(storageDirName);
+ if (storageDir.exists()) {
+ storageDir.remove(true);
+ }
+
+ const storageFile = getRelativeFile(storageFileName);
+ if (storageFile.exists()) {
+ // It could be a non empty directory, so remove it recursively.
+ storageFile.remove(true);
+ }
+ }
+ }
+}
diff --git a/dom/quota/test/xpcshell/telemetry/version0_0_make_it_unusable.zip b/dom/quota/test/xpcshell/telemetry/version0_0_make_it_unusable.zip
new file mode 100644
index 0000000000..92dcfb777e
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/version0_0_make_it_unusable.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/version0_0_profile.zip b/dom/quota/test/xpcshell/telemetry/version0_0_profile.zip
new file mode 100644
index 0000000000..2fb0f525c2
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/version0_0_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/version1_0_make_it_unusable.zip b/dom/quota/test/xpcshell/telemetry/version1_0_make_it_unusable.zip
new file mode 100644
index 0000000000..92dcfb777e
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/version1_0_make_it_unusable.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/version1_0_profile.zip b/dom/quota/test/xpcshell/telemetry/version1_0_profile.zip
new file mode 100644
index 0000000000..6169f439c1
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/version1_0_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/version2_0_make_it_unusable.zip b/dom/quota/test/xpcshell/telemetry/version2_0_make_it_unusable.zip
new file mode 100644
index 0000000000..92dcfb777e
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/version2_0_make_it_unusable.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/version2_0_profile.zip b/dom/quota/test/xpcshell/telemetry/version2_0_profile.zip
new file mode 100644
index 0000000000..465f53cea9
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/version2_0_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/version2_1_make_it_unusable.zip b/dom/quota/test/xpcshell/telemetry/version2_1_make_it_unusable.zip
new file mode 100644
index 0000000000..92dcfb777e
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/version2_1_make_it_unusable.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/version2_1_profile.zip b/dom/quota/test/xpcshell/telemetry/version2_1_profile.zip
new file mode 100644
index 0000000000..81463235ab
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/version2_1_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/version2_2_make_it_unusable.zip b/dom/quota/test/xpcshell/telemetry/version2_2_make_it_unusable.zip
new file mode 100644
index 0000000000..b6b8eecabf
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/version2_2_make_it_unusable.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/version2_2_profile.zip b/dom/quota/test/xpcshell/telemetry/version2_2_profile.zip
new file mode 100644
index 0000000000..e572726cca
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/version2_2_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/telemetry/xpcshell.ini b/dom/quota/test/xpcshell/telemetry/xpcshell.ini
new file mode 100644
index 0000000000..bb5655f103
--- /dev/null
+++ b/dom/quota/test/xpcshell/telemetry/xpcshell.ini
@@ -0,0 +1,20 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+head = head.js
+support-files =
+ version0_0_make_it_unusable.zip
+ version0_0_profile.zip
+ version1_0_make_it_unusable.zip
+ version1_0_profile.zip
+ version2_0_make_it_unusable.zip
+ version2_0_profile.zip
+ version2_1_make_it_unusable.zip
+ version2_1_profile.zip
+ version2_2_make_it_unusable.zip
+ version2_2_profile.zip
+
+[test_qm_first_initialization_attempt.js]
+skip-if = appname == "thunderbird"
diff --git a/dom/quota/test/xpcshell/tempMetadataCleanup_profile.zip b/dom/quota/test/xpcshell/tempMetadataCleanup_profile.zip
new file mode 100644
index 0000000000..da1de0979b
--- /dev/null
+++ b/dom/quota/test/xpcshell/tempMetadataCleanup_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/test_allowListFiles.js b/dom/quota/test/xpcshell/test_allowListFiles.js
new file mode 100644
index 0000000000..04c64c2ef5
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_allowListFiles.js
@@ -0,0 +1,61 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify thoes unexpected files are in the allow list of
+ * QuotaManager. They aren't expected in the repository but if there are,
+ * QuotaManager shouldn't fail to initialize an origin and getting usage, though
+ * those files aren't managed by QuotaManager.
+ */
+
+async function testSteps() {
+ const allowListFiles = [
+ ".dot-file",
+ "desktop.ini",
+ "Desktop.ini",
+ "Thumbs.db",
+ "thumbs.db",
+ ];
+
+ for (let allowListFile of allowListFiles) {
+ info("Testing " + allowListFile + " in the repository");
+
+ info("Initializing");
+
+ let request = init();
+ await requestFinished(request);
+
+ info("Creating unknown files");
+
+ for (let dir of ["persistenceType dir", "origin dir"]) {
+ let dirPath =
+ dir == "persistenceType dir"
+ ? "storage/default/"
+ : "storage/default/http+++example.com/";
+ let file = getRelativeFile(dirPath + allowListFile);
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+ }
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Resetting");
+
+ request = reset();
+ await requestFinished(request);
+
+ info("Getting usage");
+
+ request = getCurrentUsage(continueToNextStepSync);
+ await requestFinished(request);
+
+ info("Clearing");
+
+ request = clear();
+ await requestFinished(request);
+ }
+}
diff --git a/dom/quota/test/xpcshell/test_bad_origin_directory.js b/dom/quota/test/xpcshell/test_bad_origin_directory.js
new file mode 100644
index 0000000000..3058e31473
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_bad_origin_directory.js
@@ -0,0 +1,36 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function* testSteps() {
+ const invalidOrigin = {
+ url: "ftp://ftp.invalid.origin",
+ path: "storage/default/ftp+++ftp.invalid.origin",
+ };
+
+ info("Persisting an invalid origin");
+
+ let invalidPrincipal = getPrincipal(invalidOrigin.url);
+
+ let request = persist(invalidPrincipal, continueToNextStepSync);
+ yield undefined;
+
+ ok(
+ request.resultCode === NS_ERROR_FAILURE,
+ "Persist() failed because of the invalid origin"
+ );
+ ok(request.result === null, "The request result is null");
+
+ let originDir = getRelativeFile(invalidOrigin.path);
+ let exists = originDir.exists();
+ ok(!exists, "Directory for invalid origin doesn't exist");
+
+ request = persisted(invalidPrincipal, continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode === NS_OK, "Persisted() succeeded");
+ ok(!request.result, "The origin isn't persisted since the operation failed");
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/test_basics.js b/dom/quota/test/xpcshell/test_basics.js
new file mode 100644
index 0000000000..b72c2feb9f
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_basics.js
@@ -0,0 +1,143 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function* testSteps() {
+ const storageFile = "storage.sqlite";
+
+ const metadataFiles = [
+ {
+ path: "storage/permanent/chrome/.metadata",
+ shouldExistAfterInit: false,
+ },
+
+ {
+ path: "storage/permanent/chrome/.metadata-tmp",
+ shouldExistAfterInit: false,
+ },
+
+ {
+ path: "storage/permanent/chrome/.metadata-v2",
+ shouldExistAfterInit: true,
+ },
+
+ {
+ path: "storage/permanent/chrome/.metadata-v2-tmp",
+ shouldExistAfterInit: false,
+ },
+ ];
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying initialization status");
+
+ verifyInitializationStatus(false, false).then(continueToNextStepSync);
+ yield undefined;
+
+ info("Getting usage");
+
+ getCurrentUsage(grabUsageAndContinueHandler);
+ let usage = yield undefined;
+
+ ok(usage == 0, "Usage is zero");
+
+ info("Verifying initialization status");
+
+ verifyInitializationStatus(true, false).then(continueToNextStepSync);
+ yield undefined;
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying initialization status");
+
+ verifyInitializationStatus(false, false).then(continueToNextStepSync);
+ yield undefined;
+
+ info("Installing package");
+
+ // The profile contains just one empty IndexedDB database. The file
+ // create_db.js in the package was run locally, specifically it was
+ // temporarily added to xpcshell.ini and then executed:
+ // mach xpcshell-test --interactive dom/quota/test/xpcshell/create_db.js
+ installPackage("basics_profile");
+
+ info("Getting usage");
+
+ getCurrentUsage(grabUsageAndContinueHandler);
+ usage = yield undefined;
+
+ ok(usage > 0, "Usage is not zero");
+
+ info("Verifying initialization status");
+
+ verifyInitializationStatus(true, false).then(continueToNextStepSync);
+ yield undefined;
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Checking storage file");
+
+ let file = getRelativeFile(storageFile);
+
+ let exists = file.exists();
+ ok(!exists, "Storage file doesn't exist");
+
+ info("Verifying initialization status");
+
+ verifyInitializationStatus(false, false).then(continueToNextStepSync);
+ yield undefined;
+
+ info("Initializing");
+
+ request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ exists = file.exists();
+ ok(exists, "Storage file does exist");
+
+ info("Verifying initialization status");
+
+ verifyInitializationStatus(true, false).then(continueToNextStepSync);
+ yield undefined;
+
+ info("Initializing origin");
+
+ request = initPersistentOrigin(getCurrentPrincipal(), continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ ok(request.result, "Origin directory was created");
+
+ for (let metadataFile of metadataFiles) {
+ file = getRelativeFile(metadataFile.path);
+
+ exists = file.exists();
+
+ if (metadataFile.shouldExistAfterInit) {
+ ok(exists, "Metadata file does exist");
+ } else {
+ ok(!exists, "Metadata file doesn't exist");
+ }
+ }
+
+ info("Verifying initialization status");
+
+ verifyInitializationStatus(true, false).then(continueToNextStepSync);
+
+ yield undefined;
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/test_clearStoragesForOriginAttributesPattern.js b/dom/quota/test/xpcshell/test_clearStoragesForOriginAttributesPattern.js
new file mode 100644
index 0000000000..096cf2be70
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_clearStoragesForOriginAttributesPattern.js
@@ -0,0 +1,58 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+ const baseRelativePath = "storage/default";
+ const userContextForRemoval = 2;
+
+ const origins = [
+ {
+ userContextId: 1,
+ baseDirName: "https+++example.com",
+ },
+
+ {
+ userContextId: userContextForRemoval,
+ baseDirName: "https+++example.com",
+ },
+
+ // TODO: Uncomment this once bug 1638831 is fixed.
+ /*
+ {
+ userContextId: userContextForRemoval,
+ baseDirName: "https+++example.org",
+ },
+ */
+ ];
+
+ function getOriginDirectory(origin) {
+ return getRelativeFile(
+ `${baseRelativePath}/${origin.baseDirName}^userContextId=` +
+ `${origin.userContextId}`
+ );
+ }
+
+ let request = init();
+ await requestFinished(request);
+
+ for (const origin of origins) {
+ const directory = getOriginDirectory(origin);
+ directory.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+ }
+
+ request = Services.qms.clearStoragesForOriginAttributesPattern(
+ `{ "userContextId": ${userContextForRemoval} }`
+ );
+ await requestFinished(request);
+
+ for (const origin of origins) {
+ const directory = getOriginDirectory(origin);
+ if (origin.userContextId === userContextForRemoval) {
+ ok(!directory.exists(), "Origin directory should have been removed");
+ } else {
+ ok(directory.exists(), "Origin directory shouldn't have been removed");
+ }
+ }
+}
diff --git a/dom/quota/test/xpcshell/test_clearStoragesForPrincipal.js b/dom/quota/test/xpcshell/test_clearStoragesForPrincipal.js
new file mode 100644
index 0000000000..1e8bed54b7
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_clearStoragesForPrincipal.js
@@ -0,0 +1,56 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is an xpcshell test for clearStoragesForPrincipal. It verifies that
+ * if the removing client is the last client in the targeting origin, then it
+ * is expected to remove the origin directory as well.
+ */
+
+async function testSteps() {
+ const testingOrigins = [
+ {
+ origin: "http://example.com",
+ path: "storage/default/http+++example.com/",
+ only_idb: false,
+ },
+ {
+ origin: "http://www.mozilla.org",
+ path: "storage/default/http+++www.mozilla.org/",
+ only_idb: true,
+ },
+ ];
+ const removingClient = "idb";
+
+ info("Installing package to create the environment");
+ // The package is manually created and it contains:
+ // - storage/default/http+++www.mozilla.org/idb/
+ // - storage/default/http+++www.example.com/idb/
+ // - storage/default/http+++www.example.com/cache/
+ installPackage("clearStoragesForPrincipal_profile");
+
+ let request;
+ let file;
+ for (let i = 0; i < testingOrigins.length; ++i) {
+ info("Clearing");
+ request = clearClient(
+ getPrincipal(testingOrigins[i].origin),
+ null,
+ removingClient
+ );
+ await requestFinished(request);
+
+ info("Verifying");
+ file = getRelativeFile(testingOrigins[i].path + removingClient);
+ ok(!file.exists(), "Client file doesn't exist");
+
+ file = getRelativeFile(testingOrigins[i].path);
+ if (testingOrigins[i].only_idb) {
+ todo(!file.exists(), "Origin file doesn't exist");
+ } else {
+ ok(file.exists(), "Origin file does exist");
+ }
+ }
+}
diff --git a/dom/quota/test/xpcshell/test_clearStoragesForPrivateBrowsing.js b/dom/quota/test/xpcshell/test_clearStoragesForPrivateBrowsing.js
new file mode 100644
index 0000000000..6a94e5c98e
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_clearStoragesForPrivateBrowsing.js
@@ -0,0 +1,41 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify clearing of storages for private browsing.
+ */
+
+async function testSteps() {
+ const packages = [
+ "clearStoragesForPrivateBrowsing_profile",
+ "defaultStorageDirectory_shared",
+ ];
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing package");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Clearing private browsing");
+
+ request = clearPrivateBrowsing();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterClearPrivateBrowsing");
+}
diff --git a/dom/quota/test/xpcshell/test_createLocalStorage.js b/dom/quota/test/xpcshell/test_createLocalStorage.js
new file mode 100644
index 0000000000..a99a458713
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_createLocalStorage.js
@@ -0,0 +1,155 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+ const webAppsStoreFile = "webappsstore.sqlite";
+ const lsArchiveFile = "storage/ls-archive.sqlite";
+ const lsArchiveTmpFile = "storage/ls-archive-tmp.sqlite";
+
+ function checkArchiveFileNotExists() {
+ info("Checking archive tmp file");
+
+ let archiveTmpFile = getRelativeFile(lsArchiveTmpFile);
+
+ let exists = archiveTmpFile.exists();
+ ok(!exists, "archive tmp file doesn't exist");
+
+ info("Checking archive file");
+
+ let archiveFile = getRelativeFile(lsArchiveFile);
+
+ exists = archiveFile.exists();
+ ok(!exists, "archive file doesn't exist");
+ }
+
+ function checkArchiveFileExists() {
+ info("Checking archive tmp file");
+
+ let archiveTmpFile = getRelativeFile(lsArchiveTmpFile);
+
+ let exists = archiveTmpFile.exists();
+ ok(!exists, "archive tmp file doesn't exist");
+
+ info("Checking archive file");
+
+ let archiveFile = getRelativeFile(lsArchiveFile);
+
+ exists = archiveFile.exists();
+ ok(exists, "archive file does exist");
+
+ info("Checking archive file size");
+
+ let fileSize = archiveFile.fileSize;
+ ok(fileSize > 0, "archive file size is greater than zero");
+ }
+
+ // Profile 1 - Nonexistent apps store file.
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ let appsStoreFile = getRelativeFile(webAppsStoreFile);
+
+ let exists = appsStoreFile.exists();
+ ok(!exists, "apps store file doesn't exist");
+
+ checkArchiveFileNotExists();
+
+ try {
+ request = init();
+ await requestFinished(request);
+
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+
+ checkArchiveFileExists();
+
+ // Profile 2 - apps store file is a directory.
+ info("Clearing");
+
+ request = clear();
+ await requestFinished(request);
+
+ appsStoreFile.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+
+ checkArchiveFileNotExists();
+
+ try {
+ request = init();
+ await requestFinished(request);
+
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+
+ checkArchiveFileExists();
+
+ appsStoreFile.remove(true);
+
+ // Profile 3 - Corrupted apps store file.
+ info("Clearing");
+
+ request = clear();
+ await requestFinished(request);
+
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ ostream.init(appsStoreFile, -1, parseInt("0644", 8), 0);
+ ostream.write("foobar", 6);
+ ostream.close();
+
+ checkArchiveFileNotExists();
+
+ try {
+ request = init();
+ await requestFinished(request);
+
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+
+ checkArchiveFileExists();
+
+ appsStoreFile.remove(false);
+
+ // Profile 4 - Nonupdateable apps store file.
+ info("Clearing");
+
+ request = clear();
+ await requestFinished(request);
+
+ info("Installing package");
+
+ // The profile contains storage.sqlite and webappsstore.sqlite
+ // webappstore.sqlite was taken from FF 54 to force an upgrade.
+ // There's just one record in the webappsstore2 table. The record was
+ // modified by renaming the origin attribute userContextId to userContextKey.
+ // This triggers an error during the upgrade.
+ installPackage("createLocalStorage_profile");
+
+ let fileSize = appsStoreFile.fileSize;
+ ok(fileSize > 0, "apps store file size is greater than zero");
+
+ checkArchiveFileNotExists();
+
+ try {
+ request = init();
+ await requestFinished(request);
+
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+
+ checkArchiveFileExists();
+
+ appsStoreFile.remove(false);
+}
diff --git a/dom/quota/test/xpcshell/test_estimateOrigin.js b/dom/quota/test/xpcshell/test_estimateOrigin.js
new file mode 100644
index 0000000000..31d6f01686
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_estimateOrigin.js
@@ -0,0 +1,80 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+loadScript("dom/quota/test/xpcshell/common/utils.js");
+
+async function verifyOriginEstimation(principal, expectedUsage, expectedLimit) {
+ info("Estimating origin");
+
+ const request = estimateOrigin(principal);
+ await requestFinished(request);
+
+ is(request.result.usage, expectedUsage, "Correct usage");
+ is(request.result.limit, expectedLimit, "Correct limit");
+}
+
+async function testSteps() {
+ // The group limit is calculated as 20% of the global limit and the minimum
+ // value of the group limit is 10 MB.
+
+ const groupLimitKB = 10 * 1024;
+ const groupLimitBytes = groupLimitKB * 1024;
+ const globalLimitKB = groupLimitKB * 5;
+ const globalLimitBytes = globalLimitKB * 1024;
+
+ info("Setting limits");
+
+ setGlobalLimit(globalLimitKB);
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Filling origins");
+
+ await fillOrigin(getPrincipal("https://foo1.example1.com"), 100);
+ await fillOrigin(getPrincipal("https://foo2.example1.com"), 200);
+ await fillOrigin(getPrincipal("https://foo1.example2.com"), 300);
+ await fillOrigin(getPrincipal("https://foo2.example2.com"), 400);
+
+ info("Verifying origin estimations");
+
+ await verifyOriginEstimation(
+ getPrincipal("https://foo1.example1.com"),
+ 300,
+ groupLimitBytes
+ );
+ await verifyOriginEstimation(
+ getPrincipal("https://foo2.example1.com"),
+ 300,
+ groupLimitBytes
+ );
+ await verifyOriginEstimation(
+ getPrincipal("https://foo1.example2.com"),
+ 700,
+ groupLimitBytes
+ );
+ await verifyOriginEstimation(
+ getPrincipal("https://foo2.example2.com"),
+ 700,
+ groupLimitBytes
+ );
+
+ info("Persisting origin");
+
+ request = persist(getPrincipal("https://foo2.example2.com"));
+ await requestFinished(request);
+
+ info("Verifying origin estimation");
+
+ await verifyOriginEstimation(
+ getPrincipal("https://foo2.example2.com"),
+ 1000,
+ globalLimitBytes
+ );
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/test_getUsage.js b/dom/quota/test/xpcshell/test_getUsage.js
new file mode 100644
index 0000000000..7cdabcbac2
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_getUsage.js
@@ -0,0 +1,129 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function* testSteps() {
+ const origins = [
+ {
+ origin: "http://example.com",
+ persisted: false,
+ usage: 49152,
+ },
+
+ {
+ origin: "http://localhost",
+ persisted: false,
+ usage: 147456,
+ },
+
+ {
+ origin: "http://www.mozilla.org",
+ persisted: true,
+ usage: 98304,
+ },
+ ];
+
+ const allOrigins = [
+ {
+ origin: "chrome",
+ persisted: false,
+ usage: 147456,
+ },
+
+ {
+ origin: "http://example.com",
+ persisted: false,
+ usage: 49152,
+ },
+
+ {
+ origin: "http://localhost",
+ persisted: false,
+ usage: 147456,
+ },
+
+ {
+ origin: "http://www.mozilla.org",
+ persisted: true,
+ usage: 98304,
+ },
+ ];
+
+ function verifyResult(result, expectedOrigins) {
+ ok(result instanceof Array, "Got an array object");
+ ok(result.length == expectedOrigins.length, "Correct number of elements");
+
+ info("Sorting elements");
+
+ result.sort(function (a, b) {
+ let originA = a.origin;
+ let originB = b.origin;
+
+ if (originA < originB) {
+ return -1;
+ }
+ if (originA > originB) {
+ return 1;
+ }
+ return 0;
+ });
+
+ info("Verifying elements");
+
+ for (let i = 0; i < result.length; i++) {
+ let a = result[i];
+ let b = expectedOrigins[i];
+ ok(a.origin == b.origin, "Origin equals");
+ ok(a.persisted == b.persisted, "Persisted equals");
+ ok(a.usage == b.usage, "Usage equals");
+ }
+ }
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Getting usage");
+
+ getUsage(grabResultAndContinueHandler, /* getAll */ true);
+ let result = yield undefined;
+
+ info("Verifying result");
+
+ verifyResult(result, []);
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Installing package");
+
+ // The profile contains IndexedDB databases placed across the repositories.
+ // The file create_db.js in the package was run locally, specifically it was
+ // temporarily added to xpcshell.ini and then executed:
+ // mach xpcshell-test --interactive dom/quota/test/xpcshell/create_db.js
+ installPackage("getUsage_profile");
+
+ info("Getting usage");
+
+ getUsage(grabResultAndContinueHandler, /* getAll */ false);
+ result = yield undefined;
+
+ info("Verifying result");
+
+ verifyResult(result, origins);
+
+ info("Getting usage");
+
+ getUsage(grabResultAndContinueHandler, /* getAll */ true);
+ result = yield undefined;
+
+ info("Verifying result");
+
+ verifyResult(result, allOrigins);
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/test_groupMismatch.js b/dom/quota/test/xpcshell/test_groupMismatch.js
new file mode 100644
index 0000000000..41a72d51ad
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_groupMismatch.js
@@ -0,0 +1,74 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that metadata files with old group information
+ * get updated. See bug 1535995.
+ */
+
+loadScript("dom/quota/test/common/file.js");
+
+async function testSteps() {
+ const metadataFile = getRelativeFile(
+ "storage/default/https+++foo.bar.mozilla-iot.org/.metadata-v2"
+ );
+
+ async function readMetadataFile() {
+ let file = await File.createFromNsIFile(metadataFile);
+
+ let buffer = await new Promise(resolve => {
+ let reader = new FileReader();
+ reader.onloadend = () => resolve(reader.result);
+ reader.readAsArrayBuffer(file);
+ });
+
+ return buffer;
+ }
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Installing package");
+
+ // The profile contains one initialized origin directory, a script for origin
+ // initialization and the storage database:
+ // - storage/default/https+++foo.bar.mozilla-iot.org
+ // - create_db.js
+ // - storage.sqlite
+ // The file create_db.js in the package was run locally, specifically it was
+ // temporarily added to xpcshell.ini and then executed:
+ // mach xpcshell-test --interactive dom/localstorage/test/xpcshell/create_db.js
+ // Note: to make it become the profile in the test, additional manual steps
+ // are needed.
+ // 1. Manually change the group in .metadata and .metadata-v2 from
+ // "bar.mozilla-iot.org" to "mozilla-iot.org".
+ // 2. Remove the folder "storage/temporary".
+ // 3. Remove the file "storage/ls-archive.sqlite".
+ installPackage("groupMismatch_profile");
+
+ info("Reading out contents of metadata file");
+
+ let metadataBuffer = await readMetadataFile();
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Reading out contents of metadata file");
+
+ let metadataBuffer2 = await readMetadataFile();
+
+ info("Verifying blobs differ");
+
+ ok(!compareBuffers(metadataBuffer, metadataBuffer2), "Metadata differ");
+}
diff --git a/dom/quota/test/xpcshell/test_initTemporaryStorage.js b/dom/quota/test/xpcshell/test_initTemporaryStorage.js
new file mode 100644
index 0000000000..d6a6cfcb5f
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_initTemporaryStorage.js
@@ -0,0 +1,49 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify initTemporaryStorage() does call
+ * QuotaManager::EnsureTemporaryStorageIsInitialized() which does various
+ * things, for example, it restores the directory metadata if it's broken or
+ * missing.
+ */
+
+async function testSteps() {
+ const originDirPath = "storage/default/https+++foo.example.com";
+ const metadataFileName = ".metadata-v2";
+
+ info("Initializing");
+
+ let request = init();
+ await requestFinished(request);
+
+ info("Verifying initialization status");
+
+ await verifyInitializationStatus(true, false);
+
+ info("Creating an empty directory");
+
+ let originDir = getRelativeFile(originDirPath);
+ originDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+
+ info("Initializing the temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info(
+ "Verifying directory metadata was restored after calling " +
+ "initTemporaryStorage()"
+ );
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ ok(metadataFile.exists(), "Directory metadata file does exist");
+
+ info("Verifying initialization status");
+
+ await verifyInitializationStatus(true, true);
+}
diff --git a/dom/quota/test/xpcshell/test_listOrigins.js b/dom/quota/test/xpcshell/test_listOrigins.js
new file mode 100644
index 0000000000..358c87ab3a
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_listOrigins.js
@@ -0,0 +1,80 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+ const origins = [
+ "https://example.com",
+ "https://localhost",
+ "https://www.mozilla.org",
+ ];
+
+ function verifyResult(result, expectedOrigins) {
+ ok(result instanceof Array, "Got an array object");
+ ok(result.length == expectedOrigins.length, "Correct number of elements");
+
+ info("Sorting elements");
+
+ result.sort(function (a, b) {
+ if (a < b) {
+ return -1;
+ }
+ if (a > b) {
+ return 1;
+ }
+ return 0;
+ });
+
+ info("Verifying elements");
+
+ for (let i = 0; i < result.length; i++) {
+ ok(result[i] == expectedOrigins[i], "Result matches expected origin");
+ }
+ }
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Listing origins");
+
+ request = listOrigins();
+ await requestFinished(request);
+
+ info("Verifying result");
+
+ verifyResult(request.result, []);
+
+ info("Clearing");
+
+ request = clear();
+ await requestFinished(request);
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Initializing origins");
+
+ for (const origin of origins) {
+ request = initTemporaryOrigin("default", getPrincipal(origin));
+ await requestFinished(request);
+ }
+
+ info("Listing origins");
+
+ request = listOrigins();
+ await requestFinished(request);
+
+ info("Verifying result");
+
+ verifyResult(request.result, origins);
+}
diff --git a/dom/quota/test/xpcshell/test_originEndsWithDot.js b/dom/quota/test/xpcshell/test_originEndsWithDot.js
new file mode 100644
index 0000000000..8301b3292d
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_originEndsWithDot.js
@@ -0,0 +1,70 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+loadScript("dom/quota/test/common/file.js");
+
+async function testSteps() {
+ // First, ensure the origin can be initialized and used by a client that uses
+ // SQLite databases.
+
+ // Todo: consider using simpleDB once it supports using storage for SQLite.
+ info("Testing SQLite database with an origin that ends with a dot");
+
+ const principal = getPrincipal("https://example.com.");
+ let request = indexedDB.openForPrincipal(principal, "myIndexedDB");
+ await openDBRequestUpgradeNeeded(request);
+
+ info("Testing simple operations");
+
+ const database = request.result;
+
+ const objectStore = database.createObjectStore("Blobs", {});
+
+ objectStore.add(getNullBlob(200), 42);
+
+ await openDBRequestSucceeded(request);
+
+ database.close();
+
+ info("Reseting");
+
+ request = reset();
+ await requestFinished(request);
+
+ let idbDB = getRelativeFile(
+ "storage/default/https+++example.com./idb/2320029346mByDIdnedxe.sqlite"
+ );
+ ok(idbDB.exists(), "IDB database was created successfully");
+
+ // Second, ensure storage initialization works fine with the origin.
+
+ info("Testing storage initialization and temporary storage initialization");
+
+ request = init();
+ await requestFinished(request);
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ // Third, ensure QMS APIs that touch the client directory for the origin work
+ // fine.
+
+ info("Testing getUsageForPrincipal");
+
+ request = getOriginUsage(principal);
+ await requestFinished(request);
+
+ ok(
+ request.result instanceof Ci.nsIQuotaOriginUsageResult,
+ "The result is nsIQuotaOriginUsageResult instance"
+ );
+ ok(request.result.usage > 0, "Total usage is not empty");
+ ok(request.result.fileUsage > 0, "File usage is not empty");
+
+ info("Testing clearStoragesForPrincipal");
+
+ request = clearOrigin(principal, "default");
+ await requestFinished(request);
+}
diff --git a/dom/quota/test/xpcshell/test_originMismatch.js b/dom/quota/test/xpcshell/test_originMismatch.js
new file mode 100644
index 0000000000..23186977b2
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_originMismatch.js
@@ -0,0 +1,75 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that temporary storage initialization should
+ * succeed while there is an origin directory that has an inconsistency between
+ * its directory name and the origin name in its directory metadata file.
+ */
+
+async function testSteps() {
+ const packages = ["originMismatch_profile", "defaultStorageDirectory_shared"];
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing package");
+
+ // The profile contains:
+ // - storage.sqlite (v2_3)
+ // (A) Verify we are okay while the directory that we want to restore has
+ // already existed.
+ // - storage/default/http+++www.example.com/.metadata-v2
+ // (origin: http://www.example.com.)
+ // - storage/default/http+++www.example.com/cache/.padding
+ // - storage/default/http+++www.example.com./
+ // (B) Verify restoring origin directory succeed.
+ // - storage/default/http+++www.example.org/.metadata-v2
+ // (origin: http://www.example.org.)
+ // - storage/default/http+++www.example.org/cache/.padding
+ //
+ // ToDo: Test case like:
+ // - storage/default/http+++www.example.org(1)/.metadata-v2
+ // (origin: http://www.example.org)
+ // - storage/default/http+++www.example.org/
+ //
+ // - storage/default/http+++www.foo.com/.metadata-v2
+ // (origin: http://www.bar.com)
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Initializing storage");
+
+ request = init();
+ await requestFinished(request);
+
+ // ToDo: Remove this code once we support unknown directories in respository
+ // (bug 1594075).
+ let invalidDir = getRelativeFile("storage/default/invalid+++example.com");
+ invalidDir.remove(true);
+ invalidDir = getRelativeFile("storage/temporary/invalid+++example.com");
+ invalidDir.remove(true);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInitTemporaryStorage");
+
+ request = clear();
+ await requestFinished(request);
+}
diff --git a/dom/quota/test/xpcshell/test_originWithCaret.js b/dom/quota/test/xpcshell/test_originWithCaret.js
new file mode 100644
index 0000000000..7afce4f5cc
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_originWithCaret.js
@@ -0,0 +1,17 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+async function testSteps() {
+ Assert.throws(
+ () => {
+ const principal = getPrincipal("http://example.com^123");
+ getSimpleDatabase(principal);
+ },
+ /NS_ERROR_MALFORMED_URI/,
+ "^ is not allowed in the hostname"
+ );
+}
diff --git a/dom/quota/test/xpcshell/test_orpahnedQuotaObject.js b/dom/quota/test/xpcshell/test_orpahnedQuotaObject.js
new file mode 100644
index 0000000000..6cbca13d8c
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_orpahnedQuotaObject.js
@@ -0,0 +1,44 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+ const principal = getPrincipal("https://example.com");
+
+ info("Setting pref");
+
+ Services.prefs.setBoolPref("dom.storage.client_validation", false);
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Creating simpledb");
+
+ let database = getSimpleDatabase(principal);
+
+ request = database.open("data");
+ await requestFinished(request);
+
+ info("Creating localStorage");
+
+ let storage = Services.domStorageManager.createStorage(
+ null,
+ principal,
+ principal,
+ ""
+ );
+ storage.setItem("key", "value");
+
+ info("Clearing simpledb");
+
+ request = clearClient(principal, "default", "sdb");
+ await requestFinished(request);
+
+ info("Resetting localStorage");
+
+ request = resetClient(principal, "ls");
+ await requestFinished(request);
+}
diff --git a/dom/quota/test/xpcshell/test_persist.js b/dom/quota/test/xpcshell/test_persist.js
new file mode 100644
index 0000000000..0920522973
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_persist.js
@@ -0,0 +1,121 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function* testSteps() {
+ const origin = {
+ url: "http://default.test.persist",
+ path: "storage/default/http+++default.test.persist",
+ persistence: "default",
+ };
+
+ const metadataFileName = ".metadata-v2";
+
+ let principal = getPrincipal(origin.url);
+
+ info("Persisting an uninitialized origin");
+
+ // Origin directory doesn't exist yet, so only check the result for
+ // persisted().
+ let request = persisted(principal, continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode === NS_OK, "Persisted() succeeded");
+ ok(!request.result, "The origin is not persisted");
+
+ info("Verifying persist() does update the metadata");
+
+ request = persist(principal, continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode === NS_OK, "Persist() succeeded");
+
+ let originDir = getRelativeFile(origin.path);
+ let exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ info("Reading out contents of metadata file");
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ File.createFromNsIFile(metadataFile).then(grabArgAndContinueHandler);
+ let file = yield undefined;
+
+ let fileReader = new FileReader();
+ fileReader.onload = continueToNextStepSync;
+ fileReader.readAsArrayBuffer(file);
+ yield undefined;
+
+ let originPersisted = getPersistedFromMetadata(fileReader.result);
+ ok(originPersisted, "The origin is persisted");
+
+ info("Verifying persisted()");
+
+ request = persisted(principal, continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode === NS_OK, "Persisted() succeeded");
+ ok(request.result === originPersisted, "Persisted() concurs with metadata");
+
+ info("Clearing the origin");
+
+ // Clear the origin since we'll test the same directory again under different
+ // circumstances.
+ clearOrigin(principal, origin.persistence, continueToNextStepSync);
+ yield undefined;
+
+ info("Persisting an already initialized origin");
+
+ initTemporaryStorage(continueToNextStepSync);
+ yield undefined;
+
+ initTemporaryOrigin(origin.persistence, principal, continueToNextStepSync);
+ yield undefined;
+
+ info("Reading out contents of metadata file");
+
+ fileReader = new FileReader();
+ fileReader.onload = continueToNextStepSync;
+ fileReader.readAsArrayBuffer(file);
+ yield undefined;
+
+ originPersisted = getPersistedFromMetadata(fileReader.result);
+ ok(!originPersisted, "The origin isn't persisted after clearing");
+
+ info("Verifying persisted()");
+
+ request = persisted(principal, continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode === NS_OK, "Persisted() succeeded");
+ ok(request.result === originPersisted, "Persisted() concurs with metadata");
+
+ info("Verifying persist() does update the metadata");
+
+ request = persist(principal, continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode === NS_OK, "Persist() succeeded");
+
+ info("Reading out contents of metadata file");
+
+ fileReader = new FileReader();
+ fileReader.onload = continueToNextStepSync;
+ fileReader.readAsArrayBuffer(file);
+ yield undefined;
+
+ originPersisted = getPersistedFromMetadata(fileReader.result);
+ ok(originPersisted, "The origin is persisted");
+
+ info("Verifying persisted()");
+
+ request = persisted(principal, continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode === NS_OK, "Persisted() succeeded");
+ ok(request.result === originPersisted, "Persisted() concurs with metadata");
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/test_persist_eviction.js b/dom/quota/test/xpcshell/test_persist_eviction.js
new file mode 100644
index 0000000000..9a62f91b50
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_persist_eviction.js
@@ -0,0 +1,82 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that normally the oldest origin will be
+ * evicted if the global limit is reached, but if the oldest origin is
+ * persisted or is an extension origin, then it won't be evicted.
+ */
+
+loadScript("dom/quota/test/xpcshell/common/utils.js");
+
+async function testSteps() {
+ // The group limit is calculated as 20% of the global limit and the minimum
+ // value of the group limit is 10 MB.
+
+ const groupLimitKB = 10 * 1024;
+ const globalLimitKB = groupLimitKB * 5;
+
+ setGlobalLimit(globalLimitKB);
+
+ let request = clear();
+ await requestFinished(request);
+
+ for (let persistOldestOrigin of [false, true]) {
+ info(
+ "Testing " +
+ (persistOldestOrigin ? "with" : "without") +
+ " persisting the oldest origin"
+ );
+
+ info(
+ "Step 0: Filling a moz-extension origin as the oldest origin with non-persisted data"
+ );
+
+ // Just a fake moz-extension origin to mock an extension origin.
+ let extUUID = "20445ca5-75f9-420e-a1d4-9cccccb5e891";
+ let spec = `moz-extension://${extUUID}`;
+ await fillOrigin(getPrincipal(spec), groupLimitKB * 1024);
+
+ info(
+ "Step 1: Filling five separate web origins to reach the global limit " +
+ "and trigger eviction"
+ );
+
+ for (let index = 1; index <= 5; index++) {
+ let spec = "http://example" + index + ".com";
+ if (index == 1 && persistOldestOrigin) {
+ request = persist(getPrincipal(spec));
+ await requestFinished(request);
+ }
+ await fillOrigin(getPrincipal(spec), groupLimitKB * 1024);
+ }
+
+ info("Step 2: Verifying origin directories");
+
+ for (let index = 1; index <= 5; index++) {
+ let path = "storage/default/http+++example" + index + ".com";
+ let file = getRelativeFile(path);
+ if (index == (persistOldestOrigin ? 2 : 1)) {
+ ok(!file.exists(), "The origin directory " + path + " doesn't exist");
+ } else {
+ ok(file.exists(), "The origin directory " + path + " does exist");
+ }
+ }
+
+ // Verify that the extension storage data has not been evicted (even if it wasn't marked as
+ // persisted and it was the less recently used origin).
+ let path = `storage/default/moz-extension+++${extUUID}`;
+ let file = getRelativeFile(path);
+ ok(file.exists(), "The origin directory " + path + "does exist");
+
+ request = clear();
+ await requestFinished(request);
+ }
+
+ resetGlobalLimit();
+
+ request = reset();
+ await requestFinished(request);
+}
diff --git a/dom/quota/test/xpcshell/test_persist_globalLimit.js b/dom/quota/test/xpcshell/test_persist_globalLimit.js
new file mode 100644
index 0000000000..8b15c26eae
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_persist_globalLimit.js
@@ -0,0 +1,82 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that persisted origins are always bounded by
+ * the global limit.
+ */
+
+loadScript("dom/quota/test/common/file.js");
+
+async function testSteps() {
+ const globalLimitKB = 1;
+
+ const principal = getPrincipal("https://persisted.example.com");
+
+ info("Setting limits");
+
+ setGlobalLimit(globalLimitKB);
+
+ let request = clear();
+ await requestFinished(request);
+
+ for (let initializeStorageBeforePersist of [false, true]) {
+ if (initializeStorageBeforePersist) {
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Initializing the temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+ }
+
+ info("Persisting an origin");
+
+ request = persist(principal);
+ await requestFinished(request);
+
+ info("Verifying the persisted origin is bounded by global limit");
+
+ let database = getSimpleDatabase(principal);
+
+ info("Opening a database for the persisted origin");
+
+ request = database.open("data");
+ await requestFinished(request);
+
+ try {
+ info("Writing over the limit shouldn't succeed");
+
+ request = database.write(getBuffer(globalLimitKB * 1024 + 1));
+ await requestFinished(request);
+
+ ok(false, "Should have thrown");
+ } catch (e) {
+ ok(true, "Should have thrown");
+ ok(
+ e.resultCode === NS_ERROR_FILE_NO_DEVICE_SPACE,
+ "Threw right result code"
+ );
+ }
+
+ info("Closing the database and clearing");
+
+ request = database.close();
+ await requestFinished(request);
+
+ request = clear();
+ await requestFinished(request);
+ }
+
+ info("Resetting limits");
+
+ resetGlobalLimit();
+
+ request = reset();
+ await requestFinished(request);
+}
diff --git a/dom/quota/test/xpcshell/test_persist_groupLimit.js b/dom/quota/test/xpcshell/test_persist_groupLimit.js
new file mode 100644
index 0000000000..202726299f
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_persist_groupLimit.js
@@ -0,0 +1,104 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that persisted origins are not constrained by
+ * the group limit. It consits of these steps:
+ * - Set the limits as small as our limits allow. This does result in needing
+ * to perform 10 megs of writes which is a lot for a test but not horrible.
+ * - Create databases for 2 origins under the same group.
+ * - Have the foo2 origin use up the shared group quota.
+ * - Verify neither origin can write additional data (via a single byte write).
+ * - Do navigator.storage.persist() for that foo2 origin.
+ * - Verify that both origins can now write an additional byte. This
+ * demonstrates that:
+ * - foo2 no longer counts against the group limit at all since foo1 can
+ * write a byte.
+ * - foo2 is no longer constrained by the group limit itself.
+ */
+async function testSteps() {
+ // The group limit is calculated as 20% of the global limit and the minimum
+ // value of the group limit is 10 MB.
+
+ const groupLimitKB = 10 * 1024;
+ const globalLimitKB = groupLimitKB * 5;
+
+ const urls = ["http://foo1.example.com", "http://foo2.example.com"];
+
+ const foo2Index = 1;
+
+ let index;
+
+ info("Setting limits");
+
+ setGlobalLimit(globalLimitKB);
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Opening databases");
+
+ let databases = [];
+ for (index = 0; index < urls.length; index++) {
+ let database = getSimpleDatabase(getPrincipal(urls[index]));
+
+ request = database.open("data");
+ await requestFinished(request);
+
+ databases.push(database);
+ }
+
+ info("Filling up the whole group");
+
+ try {
+ request = databases[foo2Index].write(new ArrayBuffer(groupLimitKB * 1024));
+ await requestFinished(request);
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+
+ info("Verifying no more data can be written");
+
+ for (index = 0; index < urls.length; index++) {
+ try {
+ request = databases[index].write(new ArrayBuffer(1));
+ await requestFinished(request);
+ ok(false, "Should have thrown");
+ } catch (e) {
+ ok(true, "Should have thrown");
+ ok(
+ e.resultCode == NS_ERROR_FILE_NO_DEVICE_SPACE,
+ "Threw right result code"
+ );
+ }
+ }
+
+ info("Persisting origin");
+
+ request = persist(getPrincipal(urls[foo2Index]));
+ await requestFinished(request);
+
+ info("Verifying more data data can be written");
+
+ for (index = 0; index < urls.length; index++) {
+ try {
+ request = databases[index].write(new ArrayBuffer(1));
+ await requestFinished(request);
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+ }
+
+ info("Closing databases");
+
+ for (index = 0; index < urls.length; index++) {
+ request = databases[index].close();
+ await requestFinished(request);
+ }
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/test_removeLocalStorage.js b/dom/quota/test/xpcshell/test_removeLocalStorage.js
new file mode 100644
index 0000000000..bae4ad1649
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_removeLocalStorage.js
@@ -0,0 +1,89 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function* testSteps() {
+ const lsArchiveFile = "storage/ls-archive.sqlite";
+ const lsArchiveTmpFile = "storage/ls-archive-tmp.sqlite";
+ const lsDir = "storage/default/http+++localhost/ls";
+
+ info("Setting pref");
+
+ SpecialPowers.setBoolPref(
+ "dom.storage.enable_unsupported_legacy_implementation",
+ true
+ );
+
+ // Profile 1
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Installing package");
+
+ installPackage("removeLocalStorage1_profile");
+
+ info("Checking ls archive tmp file");
+
+ let archiveTmpFile = getRelativeFile(lsArchiveTmpFile);
+
+ let exists = archiveTmpFile.exists();
+ ok(exists, "ls archive tmp file does exist");
+
+ info("Initializing");
+
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Checking ls archive file");
+
+ exists = archiveTmpFile.exists();
+ ok(!exists, "ls archive tmp file doesn't exist");
+
+ // Profile 2
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Installing package");
+
+ installPackage("removeLocalStorage2_profile");
+
+ info("Checking ls archive file");
+
+ let archiveFile = getRelativeFile(lsArchiveFile);
+
+ exists = archiveFile.exists();
+ ok(exists, "ls archive file does exist");
+
+ info("Checking ls dir");
+
+ let dir = getRelativeFile(lsDir);
+
+ exists = dir.exists();
+ ok(exists, "ls directory does exist");
+
+ info("Initializing");
+
+ request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Checking ls archive file");
+
+ exists = archiveFile.exists();
+ ok(!exists, "ls archive file doesn't exist");
+
+ info("Checking ls dir");
+
+ exists = dir.exists();
+ ok(!exists, "ls directory doesn't exist");
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/test_simpledb.js b/dom/quota/test/xpcshell/test_simpledb.js
new file mode 100644
index 0000000000..b698d778e0
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_simpledb.js
@@ -0,0 +1,6 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+loadScript("dom/quota/test/common/test_simpledb.js");
diff --git a/dom/quota/test/xpcshell/test_specialOrigins.js b/dom/quota/test/xpcshell/test_specialOrigins.js
new file mode 100644
index 0000000000..d66700d359
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_specialOrigins.js
@@ -0,0 +1,55 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+ const origins = [
+ {
+ path: "storage/default/file+++UNIVERSAL_FILE_URI_ORIGIN",
+ url: "file:///Test/test.html",
+ persistence: "default",
+ },
+ ];
+
+ info("Setting pref");
+
+ SpecialPowers.setBoolPref("security.fileuri.strict_origin_policy", false);
+
+ info("Initializing");
+
+ let request = init();
+ await requestFinished(request);
+
+ info("Creating origin directories");
+
+ for (let origin of origins) {
+ let originDir = getRelativeFile(origin.path);
+ originDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+ }
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Initializing origin directories");
+
+ for (let origin of origins) {
+ let result;
+
+ try {
+ request = initTemporaryOrigin(
+ origin.persistence,
+ getPrincipal(origin.url)
+ );
+ result = await requestFinished(request);
+
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+
+ ok(!result, "Origin directory wasn't created");
+ }
+}
diff --git a/dom/quota/test/xpcshell/test_storagePressure.js b/dom/quota/test/xpcshell/test_storagePressure.js
new file mode 100644
index 0000000000..b3c0cb5005
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_storagePressure.js
@@ -0,0 +1,135 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that the storage pressure event is fired when
+ * the eviction process is not able to free some space when a quota client
+ * attempts to write over the global limit or when the global limit is reduced
+ * below the global usage.
+ */
+
+loadScript("dom/quota/test/common/file.js");
+
+function awaitStoragePressure() {
+ let promise_resolve;
+
+ let promise = new Promise(function (resolve) {
+ promise_resolve = resolve;
+ });
+
+ function observer(subject, topic) {
+ ok(true, "Got the storage pressure event");
+
+ Services.obs.removeObserver(observer, topic);
+
+ let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ promise_resolve(usage);
+ }
+
+ Services.obs.addObserver(observer, "QuotaManager::StoragePressure");
+
+ return promise;
+}
+
+async function testSteps() {
+ const globalLimitKB = 2;
+
+ const principal = getPrincipal("https://example.com");
+
+ info("Setting limits");
+
+ setGlobalLimit(globalLimitKB);
+
+ info("Initializing");
+
+ let request = init();
+ await requestFinished(request);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Persisting and filling an origin");
+
+ // We need to persist the origin first to omit the group limit checks.
+ // Otherwise, we would have to fill five separate origins.
+ request = persist(principal);
+ await requestFinished(request);
+
+ let database = getSimpleDatabase(principal);
+
+ request = database.open("data");
+ await requestFinished(request);
+
+ try {
+ request = database.write(getBuffer(globalLimitKB * 1024));
+ await requestFinished(request);
+
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+
+ info("Testing storage pressure by writing over the global limit");
+
+ info("Storing one more byte to get the storage pressure event while writing");
+
+ let promiseStoragePressure = awaitStoragePressure();
+
+ try {
+ request = database.write(getBuffer(1));
+ await requestFinished(request);
+
+ ok(false, "Should have thrown");
+ } catch (e) {
+ ok(true, "Should have thrown");
+ ok(
+ e.resultCode === NS_ERROR_FILE_NO_DEVICE_SPACE,
+ "Threw right result code"
+ );
+ }
+
+ info("Checking the storage pressure event");
+
+ let usage = await promiseStoragePressure;
+ ok(usage == globalLimitKB * 1024, "Got correct usage");
+
+ info("Testing storage pressure by reducing the global limit");
+
+ info(
+ "Reducing the global limit to get the storage pressuse event while the" +
+ " temporary storage is being initialized"
+ );
+
+ setGlobalLimit(globalLimitKB - 1);
+
+ request = reset();
+ await requestFinished(request);
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ promiseStoragePressure = awaitStoragePressure();
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Checking the storage pressure event");
+
+ usage = await promiseStoragePressure;
+ ok(usage == globalLimitKB * 1024, "Got correct usage");
+
+ info("Resetting limits");
+
+ resetGlobalLimit();
+
+ request = reset();
+ await requestFinished(request);
+}
diff --git a/dom/quota/test/xpcshell/test_tempMetadataCleanup.js b/dom/quota/test/xpcshell/test_tempMetadataCleanup.js
new file mode 100644
index 0000000000..981e1a14d3
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_tempMetadataCleanup.js
@@ -0,0 +1,45 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function* testSteps() {
+ const tempMetadataFiles = [
+ "storage/permanent/chrome/.metadata-tmp",
+ "storage/permanent/chrome/.metadata-v2-tmp",
+ ];
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Installing package");
+
+ installPackage("tempMetadataCleanup_profile");
+
+ info("Initializing");
+
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ info("Initializing origin");
+
+ request = initPersistentOrigin(getCurrentPrincipal(), continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ ok(!request.result, "Origin directory wasn't created");
+
+ for (let tempMetadataFile of tempMetadataFiles) {
+ info("Checking temp metadata file");
+
+ let file = getRelativeFile(tempMetadataFile);
+
+ let exists = file.exists();
+ ok(!exists, "Temp metadata file doesn't exist");
+ }
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/test_unaccessedOrigins.js b/dom/quota/test/xpcshell/test_unaccessedOrigins.js
new file mode 100644
index 0000000000..93b38501f4
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_unaccessedOrigins.js
@@ -0,0 +1,168 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const SEC_PER_MONTH = 60 * 60 * 24 * 30;
+
+async function testSteps() {
+ function getHostname(index) {
+ return "www.example" + index + ".com";
+ }
+
+ function getOrigin(index) {
+ return "https://" + getHostname(index);
+ }
+
+ function getOriginDir(index) {
+ return getRelativeFile("storage/default/https+++" + getHostname(index));
+ }
+
+ function updateOriginLastAccessTime(index, deltaSec) {
+ let originDir = getOriginDir(index);
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(".metadata-v2");
+
+ let fileRandomAccessStream = Cc[
+ "@mozilla.org/network/file-random-access-stream;1"
+ ].createInstance(Ci.nsIFileRandomAccessStream);
+ fileRandomAccessStream.init(metadataFile, -1, -1, 0);
+
+ let binaryInputStream = Cc[
+ "@mozilla.org/binaryinputstream;1"
+ ].createInstance(Ci.nsIBinaryInputStream);
+ binaryInputStream.setInputStream(fileRandomAccessStream);
+
+ let lastAccessTime = binaryInputStream.read64();
+
+ let seekableStream = fileRandomAccessStream.QueryInterface(
+ Ci.nsISeekableStream
+ );
+ seekableStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+
+ binaryOutputStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
+ Ci.nsIBinaryOutputStream
+ );
+ binaryOutputStream.setOutputStream(fileRandomAccessStream);
+
+ binaryOutputStream.write64(lastAccessTime + deltaSec * PR_USEC_PER_SEC);
+
+ binaryOutputStream.close();
+
+ binaryInputStream.close();
+ }
+
+ function verifyOriginDir(index, shouldExist) {
+ let originDir = getOriginDir(index);
+ let exists = originDir.exists();
+ if (shouldExist) {
+ ok(exists, "Origin directory does exist");
+ } else {
+ ok(!exists, "Origin directory doesn't exist");
+ }
+ }
+
+ info("Setting prefs");
+
+ Services.prefs.setBoolPref("dom.quotaManager.loadQuotaFromCache", false);
+ Services.prefs.setBoolPref("dom.quotaManager.checkQuotaInfoLoadTime", true);
+ Services.prefs.setIntPref(
+ "dom.quotaManager.longQuotaInfoLoadTimeThresholdMs",
+ 0
+ );
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Initializing origins");
+
+ for (let index = 0; index < 30; index++) {
+ request = initTemporaryOrigin("default", getPrincipal(getOrigin(index)));
+ await requestFinished(request);
+ }
+
+ info("Updating last access time of selected origins");
+
+ for (let index = 0; index < 10; index++) {
+ updateOriginLastAccessTime(index, -14 * SEC_PER_MONTH);
+ }
+
+ for (let index = 10; index < 20; index++) {
+ updateOriginLastAccessTime(index, -7 * SEC_PER_MONTH);
+ }
+
+ info("Resetting");
+
+ request = reset();
+ await requestFinished(request);
+
+ info("Setting pref");
+
+ Services.prefs.setIntPref(
+ "dom.quotaManager.unaccessedForLongTimeThresholdSec",
+ 13 * SEC_PER_MONTH
+ );
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Verifying origin directories");
+
+ for (let index = 0; index < 10; index++) {
+ verifyOriginDir(index, false);
+ }
+ for (let index = 10; index < 30; index++) {
+ verifyOriginDir(index, true);
+ }
+
+ info("Resetting");
+
+ request = reset();
+ await requestFinished(request);
+
+ info("Setting pref");
+
+ Services.prefs.setIntPref(
+ "dom.quotaManager.unaccessedForLongTimeThresholdSec",
+ 6 * SEC_PER_MONTH
+ );
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Verifying origin directories");
+
+ for (let index = 0; index < 20; index++) {
+ verifyOriginDir(index, false);
+ }
+ for (let index = 20; index < 30; index++) {
+ verifyOriginDir(index, true);
+ }
+
+ info("Resetting");
+
+ request = reset();
+ await requestFinished(request);
+}
diff --git a/dom/quota/test/xpcshell/test_unknownFiles.js b/dom/quota/test/xpcshell/test_unknownFiles.js
new file mode 100644
index 0000000000..57918eb0ad
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_unknownFiles.js
@@ -0,0 +1,106 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that init, initTemporaryStorage,
+ * getUsageForPrincipal and clearStoragesForPrincipal are able to ignore
+ * unknown files and directories in the storage/default directory and its
+ * subdirectories.
+ */
+async function testSteps() {
+ const principal = getPrincipal("http://example.com");
+
+ async function testFunctionality(testFunction) {
+ const modes = [
+ {
+ initializedStorage: false,
+ initializedTemporaryStorage: false,
+ },
+ {
+ initializedStorage: true,
+ initializedTemporaryStorage: false,
+ },
+ {
+ initializedStorage: true,
+ initializedTemporaryStorage: true,
+ },
+ ];
+
+ for (const mode of modes) {
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Installing package");
+
+ // The profile contains unknown files and unknown directories placed
+ // across the repositories, origin directories and client directories.
+ // The file make_unknownFiles.js was run locally, specifically it was
+ // temporarily enabled in xpcshell.ini and then executed:
+ // mach test --interactive dom/quota/test/xpcshell/make_unknownFiles.js
+ installPackage("unknownFiles_profile");
+
+ if (mode.initializedStorage) {
+ info("Initializing storage");
+
+ request = init();
+ await requestFinished(request);
+ }
+
+ if (mode.initializedTemporaryStorage) {
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+ }
+
+ info("Verifying initialization status");
+
+ await verifyInitializationStatus(
+ mode.initializedStorage,
+ mode.initializedTemporaryStorage
+ );
+
+ await testFunction(
+ mode.initializedStorage,
+ mode.initializedTemporaryStorage
+ );
+
+ info("Clearing");
+
+ request = clear();
+ await requestFinished(request);
+ }
+ }
+
+ // init and initTemporaryStorage functionality is tested in the
+ // testFunctionality function as part of the multi mode testing
+
+ info("Testing getUsageForPrincipal functionality");
+
+ await testFunctionality(async function () {
+ info("Getting origin usage");
+
+ request = getOriginUsage(principal);
+ await requestFinished(request);
+
+ ok(
+ request.result instanceof Ci.nsIQuotaOriginUsageResult,
+ "The result is nsIQuotaOriginUsageResult instance"
+ );
+ is(request.result.usage, 115025, "Correct total usage");
+ is(request.result.fileUsage, 200, "Correct file usage");
+ });
+
+ info("Testing clearStoragesForPrincipal functionality");
+
+ await testFunctionality(async function () {
+ info("Clearing origin");
+
+ request = clearOrigin(principal, "default");
+ await requestFinished(request);
+ });
+}
diff --git a/dom/quota/test/xpcshell/test_unsetLastAccessTime.js b/dom/quota/test/xpcshell/test_unsetLastAccessTime.js
new file mode 100644
index 0000000000..96155af397
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_unsetLastAccessTime.js
@@ -0,0 +1,68 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function testSteps() {
+ const metadataFile = getRelativeFile(
+ "storage/default/https+++foo.example.com/.metadata-v2"
+ );
+
+ function getLastAccessTime() {
+ let fileInputStream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+
+ fileInputStream.init(metadataFile, -1, -1, 0);
+
+ let binaryInputStream = Cc[
+ "@mozilla.org/binaryinputstream;1"
+ ].createInstance(Ci.nsIBinaryInputStream);
+
+ binaryInputStream.setInputStream(fileInputStream);
+
+ let lastAccessTime = BigInt.asIntN(64, BigInt(binaryInputStream.read64()));
+
+ binaryInputStream.close();
+
+ return lastAccessTime;
+ }
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Installing package");
+
+ // The profile contains one initialized origin directory and the storage
+ // database:
+ // - storage/default/https+++foo.example.com
+ // - storage.sqlite
+ // The file make_unsetLastAccessTime.js was run locally, specifically it was
+ // temporarily enabled in xpcshell.ini and then executed:
+ // mach test --interactive dom/quota/test/xpcshell/make_unsetLastAccessTime.js
+ // Note: to make it become the profile in the test, additional manual steps
+ // are needed.
+ // 1. Remove the folder "storage/temporary".
+ // 2. Remove the file "storage/ls-archive.sqlite".
+ installPackage("unsetLastAccessTime_profile");
+
+ info("Verifying last access time");
+
+ ok(getLastAccessTime() == INT64_MIN, "Correct last access time");
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Verifying last access time");
+
+ ok(getLastAccessTime() != INT64_MIN, "Correct last access time");
+}
diff --git a/dom/quota/test/xpcshell/test_validOrigins.js b/dom/quota/test/xpcshell/test_validOrigins.js
new file mode 100644
index 0000000000..4c758d6bf5
--- /dev/null
+++ b/dom/quota/test/xpcshell/test_validOrigins.js
@@ -0,0 +1,99 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Use initOrigin to test the operation of the origin parser on a list of URLs
+// we should support. If the origin doesn't parse, then initOrigin will throw an
+// exception (and potentially MOZ_ASSERT under debug builds). Handling of
+// obsolete or invalid origins is handled in other test files.
+async function testSteps() {
+ const basePath = "storage/default/";
+ const longExampleOriginSubstring = "a".repeat(
+ 255 - "https://example..com".length
+ );
+ const origins = [
+ // General
+ {
+ dirName: "https+++example.com",
+ url: "https://example.com",
+ },
+ {
+ dirName: "https+++smaug----.github.io",
+ url: "https://smaug----.github.io/",
+ },
+ // About
+ {
+ dirName: "about+home",
+ url: "about:home",
+ },
+ {
+ dirName: "about+reader",
+ url: "about:reader",
+ },
+ // IPv6
+ {
+ dirName: "https+++[++]",
+ url: "https://[::]",
+ },
+ {
+ dirName: "https+++[ffff+ffff+ffff+ffff+ffff+ffff+ffff+ffff]",
+ url: "https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]",
+ },
+ {
+ dirName: "http+++[2010+836b+4179++836b+4179]",
+ url: "http://[2010:836B:4179::836B:4179]:80",
+ },
+ {
+ dirName: "https+++[++ffff+8190+3426]",
+ url: "https://[::FFFF:129.144.52.38]",
+ },
+ // MAX_PATH on Windows (260); storage/default/https+++example.{a....a}.com
+ // should have already exceeded the MAX_PATH limitation on Windows.
+ // There is a limitation (255) for each component on Windows so that we can
+ // only let the component be 255 chars and expect the wwhole path to be
+ // greater then 260.
+ {
+ dirName: `https+++example.${longExampleOriginSubstring}.com`,
+ url: `https://example.${longExampleOriginSubstring}.com`,
+ },
+ // EndingWithPeriod
+ {
+ dirName: "https+++example.com.",
+ url: "https://example.com.",
+ },
+ ];
+
+ info("Initializing");
+
+ let request = init();
+ await requestFinished(request);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ for (let origin of origins) {
+ info(`Testing ${origin.url}`);
+
+ try {
+ request = initTemporaryOrigin("default", getPrincipal(origin.url));
+ await requestFinished(request);
+
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+
+ let dir = getRelativeFile(basePath + origin.dirName);
+ ok(dir.exists(), "Origin was created");
+ ok(
+ origin.dirName === dir.leafName,
+ `Origin ${origin.dirName} was created expectedly`
+ );
+ }
+
+ request = clear();
+ await requestFinished(request);
+}
diff --git a/dom/quota/test/xpcshell/unknownFiles_profile.zip b/dom/quota/test/xpcshell/unknownFiles_profile.zip
new file mode 100644
index 0000000000..96d636780d
--- /dev/null
+++ b/dom/quota/test/xpcshell/unknownFiles_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/unsetLastAccessTime_profile.zip b/dom/quota/test/xpcshell/unsetLastAccessTime_profile.zip
new file mode 100644
index 0000000000..2b14ca7276
--- /dev/null
+++ b/dom/quota/test/xpcshell/unsetLastAccessTime_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/cacheVersion1_profile.json b/dom/quota/test/xpcshell/upgrades/cacheVersion1_profile.json
new file mode 100644
index 0000000000..f04f79a6d7
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/cacheVersion1_profile.json
@@ -0,0 +1,64 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "https+++www.mozilla.org^userContextId=1",
+ "dir": true,
+ "entries": [
+ {
+ "name": "sdb",
+ "dir": true,
+ "entries": [{ "name": "data.sdb", "dir": false }]
+ },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "https+++www.mozilla.org^userContextId=1",
+ "dir": true,
+ "entries": [
+ {
+ "name": "sdb",
+ "dir": true,
+ "entries": [{ "name": "data.sdb", "dir": false }]
+ },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/cacheVersion1_profile.zip b/dom/quota/test/xpcshell/upgrades/cacheVersion1_profile.zip
new file mode 100644
index 0000000000..c09b503c18
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/cacheVersion1_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/head.js b/dom/quota/test/xpcshell/upgrades/head.js
new file mode 100644
index 0000000000..5c36d82ca6
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/head.js
@@ -0,0 +1,14 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// The path to the top level directory.
+const depth = "../../../../../";
+
+loadScript("dom/quota/test/xpcshell/common/head.js");
+
+function loadScript(path) {
+ let uri = Services.io.newFileURI(do_get_file(depth + path));
+ Services.scriptloader.loadSubScript(uri.spec);
+}
diff --git a/dom/quota/test/xpcshell/upgrades/indexedDBAndPersistentStorageDirectory_profile.json b/dom/quota/test/xpcshell/upgrades/indexedDBAndPersistentStorageDirectory_profile.json
new file mode 100644
index 0000000000..db66d824eb
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/indexedDBAndPersistentStorageDirectory_profile.json
@@ -0,0 +1,63 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "indexedDB",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ }
+ ]
+ },
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "persistent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/indexedDBAndPersistentStorageDirectory_profile.zip b/dom/quota/test/xpcshell/upgrades/indexedDBAndPersistentStorageDirectory_profile.zip
new file mode 100644
index 0000000000..63936ecf9a
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/indexedDBAndPersistentStorageDirectory_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_flatOriginDirectories_profile.json b/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_flatOriginDirectories_profile.json
new file mode 100644
index 0000000000..7916c25b73
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_flatOriginDirectories_profile.json
@@ -0,0 +1,55 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "indexedDB",
+ "dir": true,
+ "entries": [
+ { "name": "1007+f+app+++system.gaiamobile.org", "dir": true },
+ { "name": "http+++www.mozilla.org", "dir": true },
+ { "name": "1007+t+https+++developer.cdn.mozilla.net", "dir": true }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "todo": "This shouldn't exist, it regressed after accidental changes done in bug 1320404",
+ "name": "https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_flatOriginDirectories_profile.zip b/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_flatOriginDirectories_profile.zip
new file mode 100644
index 0000000000..a0a56a77df
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_flatOriginDirectories_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_profile.json b/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_profile.json
new file mode 100644
index 0000000000..715c954915
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_profile.json
@@ -0,0 +1,65 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "indexedDB",
+ "dir": true,
+ "entries": [
+ {
+ "name": "1007+f+app+++system.gaiamobile.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "1007+t+https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "todo": "This shouldn't exist, it regressed after accidental changes done in bug 1320404",
+ "name": "https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_profile.zip b/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_profile.zip
new file mode 100644
index 0000000000..589e65ec82
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/localStorageArchive1upgrade_profile.zip b/dom/quota/test/xpcshell/upgrades/localStorageArchive1upgrade_profile.zip
new file mode 100644
index 0000000000..a17b90dedf
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/localStorageArchive1upgrade_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/localStorageArchive4upgrade_profile.zip b/dom/quota/test/xpcshell/upgrades/localStorageArchive4upgrade_profile.zip
new file mode 100644
index 0000000000..cf5b29adae
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/localStorageArchive4upgrade_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/localStorageArchiveDowngrade_profile.zip b/dom/quota/test/xpcshell/upgrades/localStorageArchiveDowngrade_profile.zip
new file mode 100644
index 0000000000..2f11abe858
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/localStorageArchiveDowngrade_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/persistentAndDefaultStorageDirectory_profile.json b/dom/quota/test/xpcshell/upgrades/persistentAndDefaultStorageDirectory_profile.json
new file mode 100644
index 0000000000..a25f257573
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/persistentAndDefaultStorageDirectory_profile.json
@@ -0,0 +1,63 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ }
+ ]
+ },
+ {
+ "name": "persistent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/persistentAndDefaultStorageDirectory_profile.zip b/dom/quota/test/xpcshell/upgrades/persistentAndDefaultStorageDirectory_profile.zip
new file mode 100644
index 0000000000..9ddd9af6a9
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/persistentAndDefaultStorageDirectory_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_flatOriginDirectories_profile.json b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_flatOriginDirectories_profile.json
new file mode 100644
index 0000000000..c80a3cc283
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_flatOriginDirectories_profile.json
@@ -0,0 +1,64 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "persistent",
+ "dir": true,
+ "entries": [
+ { "name": "1007+f+app+++system.gaiamobile.org", "dir": true },
+ {
+ "name": "1007+t+https+++developer.cdn.mozilla.net",
+ "dir": true
+ },
+ { "name": "http+++www.mozilla.org", "dir": true }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "todo": "This shouldn't exist, it regressed after accidental changes done in bug 1320404",
+ "name": "https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_flatOriginDirectories_profile.zip b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_flatOriginDirectories_profile.zip
new file mode 100644
index 0000000000..436ebf9070
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_flatOriginDirectories_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_originDirectories_profile.json b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_originDirectories_profile.json
new file mode 100644
index 0000000000..98d23af161
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_originDirectories_profile.json
@@ -0,0 +1,92 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "persistent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "1007+f+app+++system.gaiamobile.org",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false }
+ ]
+ },
+ {
+ "name": "1007+t+https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false }
+ ]
+ },
+ { "name": "http+++www.mozilla.org+8080", "dir": true }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++www.mozilla.org+8080",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "todo": "This shouldn't exist, it regressed after accidental changes done in bug 1320404",
+ "name": "https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_originDirectories_profile.zip b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_originDirectories_profile.zip
new file mode 100644
index 0000000000..0c5200adf4
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_originDirectories_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_profile.json b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_profile.json
new file mode 100644
index 0000000000..f5d6a4a749
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_profile.json
@@ -0,0 +1,382 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "persistent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "1007+f+app+++system.gaiamobile.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "file++++Users+joe+c+++index.html",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "moz-safe-about+home",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "https+++www.mozilla.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "file++++",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "file++++Users+joe+index.html",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "1007+t+https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "http+++localhost",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "file++++c++",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "file++++c++Users+joe+index.html",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "file++++c++Users+joe+",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "chrome",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "http+++127.0.0.1",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "file++++++index.html",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "http+++www.mozilla.org+8080",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "moz-safe-about+++home",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "resource+++fx-share-addon-at-mozilla-dot-org-fx-share-addon-data",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "indexeddb+++fx-devtools",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "https+++www.mozilla.org+8080",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "file++++Users+joe+",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ {
+ "name": "1007+f+app+++system.gaiamobile.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "1007+t+https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "http+++localhost",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "http+++localhost+82",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "chrome",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ { "name": "http+++localhost+81", "dir": true }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "file++++Users+joe+c+++index.html",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "https+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "file++++",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "file++++Users+joe+index.html",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++localhost",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "file++++c++",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "todo": "This shouldn't exist, it regressed after accidental changes done in bug 1320404",
+ "name": "https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "file++++c++Users+joe+index.html",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "file++++c++Users+joe+",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++127.0.0.1",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "file++++++index.html",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++www.mozilla.org+8080",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "https+++www.mozilla.org+8080",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "file++++Users+joe+",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++localhost",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++localhost+82",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "todo": "This shouldn't exist, it regressed after accidental changes done in bug 1320404",
+ "name": "https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "chrome",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++localhost+81",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "moz-safe-about+home",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "chrome",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "resource+++fx-share-addon-at-mozilla-dot-org-fx-share-addon-data",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "indexeddb+++fx-devtools",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_profile.zip b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_profile.zip
new file mode 100644
index 0000000000..ab8c045be9
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/test_localStorageArchive1upgrade.js b/dom/quota/test/xpcshell/upgrades/test_localStorageArchive1upgrade.js
new file mode 100644
index 0000000000..f697d9167e
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_localStorageArchive1upgrade.js
@@ -0,0 +1,65 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that local storage directories are removed
+ * during local storage archive upgrade from version 0 to version 1.
+ * See bug 1546305.
+ */
+
+async function testSteps() {
+ const lsDirs = [
+ "storage/default/http+++example.com/ls",
+ "storage/default/http+++localhost/ls",
+ "storage/default/http+++www.mozilla.org/ls",
+ ];
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Installing package");
+
+ // The profile contains three initialized origin directories with local
+ // storage data, local storage archive, a script for origin initialization,
+ // the storage database and the web apps store database:
+ // - storage/default/https+++example.com
+ // - storage/default/https+++localhost
+ // - storage/default/https+++www.mozilla.org
+ // - storage/ls-archive.sqlite
+ // - create_db.js
+ // - storage.sqlite
+ // - webappsstore.sqlite
+ // The file create_db.js in the package was run locally (with a build that
+ // doesn't support local storage archive upgrades), specifically it was
+ // temporarily added to xpcshell.ini and then executed:
+ // mach xpcshell-test --interactive dom/localstorage/test/xpcshell/create_db.js
+ // Note: to make it become the profile in the test, additional manual steps
+ // are needed.
+ // 1. Remove the folder "storage/temporary".
+ installPackage("localStorageArchive1upgrade_profile");
+
+ info("Checking ls dirs");
+
+ for (let lsDir of lsDirs) {
+ let dir = getRelativeFile(lsDir);
+
+ exists = dir.exists();
+ ok(exists, "ls directory does exist");
+ }
+
+ request = init();
+ request = await requestFinished(request);
+
+ info("Checking ls dirs");
+
+ for (let lsDir of lsDirs) {
+ let dir = getRelativeFile(lsDir);
+
+ exists = dir.exists();
+ ok(!exists, "ls directory doesn't exist");
+ }
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_localStorageArchive4upgrade.js b/dom/quota/test/xpcshell/upgrades/test_localStorageArchive4upgrade.js
new file mode 100644
index 0000000000..0b0fcfccd6
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_localStorageArchive4upgrade.js
@@ -0,0 +1,107 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that local storage directories are removed
+ * during local storage archive upgrade from version 3 to version 4.
+ * See bug 1549654.
+ */
+
+async function testSteps() {
+ const lsDirs = [
+ "storage/default/http+++localhost/ls",
+ "storage/default/http+++www.mozilla.org/ls",
+ "storage/default/http+++example.com/ls",
+ ];
+
+ const principalInfos = [
+ "http://localhost",
+ "http://www.mozilla.org",
+ "http://example.com",
+ ];
+
+ const data = [
+ { key: "foo0", value: "bar" },
+ { key: "foo1", value: "A" },
+ { key: "foo2", value: "A".repeat(100) },
+ ];
+
+ function getLocalStorage(principal) {
+ return Services.domStorageManager.createStorage(
+ null,
+ principal,
+ principal,
+ ""
+ );
+ }
+
+ info("Setting pref");
+
+ // xpcshell globals don't have associated clients in the Clients API sense, so
+ // we need to disable client validation so that this xpcshell test is allowed
+ // to use LocalStorage.
+ Services.prefs.setBoolPref("dom.storage.client_validation", false);
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Installing package");
+
+ // The profile contains three initialized origin directories with local
+ // storage data, local storage archive, a script for origin initialization,
+ // the storage database and the web apps store database:
+ // - storage/default/https+++example.com
+ // - storage/default/https+++localhost
+ // - storage/default/https+++www.mozilla.org
+ // - storage/ls-archive.sqlite
+ // - create_db.js
+ // - storage.sqlite
+ // - webappsstore.sqlite
+ // The file create_db.js in the package was run locally (with a build with
+ // local storage archive version 3), specifically it was temporarily added to
+ // xpcshell.ini and then executed:
+ // mach xpcshell-test --interactive dom/localstorage/test/xpcshell/create_db.js
+ // Note: to make it become the profile in the test, additional manual steps
+ // are needed.
+ // 1. Remove the folder "storage/temporary".
+ installPackage("localStorageArchive4upgrade_profile");
+
+ info("Checking ls dirs");
+
+ for (let lsDir of lsDirs) {
+ let dir = getRelativeFile(lsDir);
+
+ exists = dir.exists();
+ ok(exists, "ls directory does exist");
+ }
+
+ request = init();
+ request = await requestFinished(request);
+
+ info("Checking ls dirs");
+
+ for (let lsDir of lsDirs) {
+ let dir = getRelativeFile(lsDir);
+
+ exists = dir.exists();
+ ok(!exists, "ls directory doesn't exist");
+ }
+
+ info("Getting storages");
+
+ let storages = [];
+ for (let i = 0; i < principalInfos.length; i++) {
+ let storage = getLocalStorage(getPrincipal(principalInfos[i]));
+ storages.push(storage);
+ }
+
+ info("Verifying data");
+
+ for (let i = 0; i < storages.length; i++) {
+ is(storages[i].getItem(data[i].key), data[i].value, "Correct value");
+ }
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_localStorageArchiveDowngrade.js b/dom/quota/test/xpcshell/upgrades/test_localStorageArchiveDowngrade.js
new file mode 100644
index 0000000000..8ca46f01d5
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_localStorageArchiveDowngrade.js
@@ -0,0 +1,66 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that local storage directories are removed
+ * during local storage archive downgrade from any future version to current
+ * version. See bug 1546305.
+ */
+
+async function testSteps() {
+ const lsDirs = [
+ "storage/default/http+++example.com/ls",
+ "storage/default/http+++localhost/ls",
+ "storage/default/http+++www.mozilla.org/ls",
+ ];
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Installing package");
+
+ // The profile contains three initialized origin directories with local
+ // storage data, local storage archive, a script for origin initialization,
+ // the storage database and the web apps store database:
+ // - storage/default/https+++example.com
+ // - storage/default/https+++localhost
+ // - storage/default/https+++www.mozilla.org
+ // - storage/ls-archive.sqlite
+ // - create_db.js
+ // - storage.sqlite
+ // - webappsstore.sqlite
+ // The file create_db.js in the package was run locally (with a build that
+ // supports local storage archive upgrades and local storage archive version
+ // set to max integer), specifically it was temporarily added to xpcshell.ini
+ // and then executed:
+ // mach xpcshell-test --interactive dom/localstorage/test/xpcshell/create_db.js
+ // Note: to make it become the profile in the test, additional manual steps
+ // are needed.
+ // 1. Remove the folder "storage/temporary".
+ installPackage("localStorageArchiveDowngrade_profile");
+
+ info("Checking ls dirs");
+
+ for (let lsDir of lsDirs) {
+ let dir = getRelativeFile(lsDir);
+
+ exists = dir.exists();
+ ok(exists, "ls directory does exist");
+ }
+
+ request = init();
+ request = await requestFinished(request);
+
+ info("Checking ls dirs");
+
+ for (let lsDir of lsDirs) {
+ let dir = getRelativeFile(lsDir);
+
+ exists = dir.exists();
+ ok(!exists, "ls directory doesn't exist");
+ }
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeCacheFrom1.js b/dom/quota/test/xpcshell/upgrades/test_upgradeCacheFrom1.js
new file mode 100644
index 0000000000..e9424e20c6
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeCacheFrom1.js
@@ -0,0 +1,79 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify UpgradeCacheFrom1To2 method.
+ */
+
+async function testSteps() {
+ const packages = [
+ // Storage used prior FF 88 (cache version 1).
+ // The profile contains one initialized origin directory with simple
+ // database data, a script for origin initialization and the storage
+ // database:
+ // - storage/default/https+++www.mozilla.org^userContextId=1
+ // - create_db.js
+ // - storage.sqlite
+ // The file create_db.js in the package was run locally, specifically it was
+ // temporarily added to xpcshell.ini and then executed:
+ // mach xpcshell-test dom/quota/test/xpcshell/upgrades/create_db.js
+ // --interactive
+ // Note: to make it become the profile in the test, additional manual steps
+ // are needed.
+ // 1. Remove the folder "storage/temporary".
+ // 2. Remove the file "storage/ls-archive.sqlite".
+ "cacheVersion1_profile",
+ "../defaultStorageDirectory_shared",
+ ];
+ const principal = getPrincipal("https://www.mozilla.org", {
+ userContextId: 1,
+ });
+ const originUsage = 100;
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing package");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ // TODO: Remove this block once temporary storage initialization is able to
+ // ignore unknown directories.
+ getRelativeFile("storage/default/invalid+++example.com").remove(false);
+ getRelativeFile("storage/temporary/invalid+++example.com").remove(false);
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage(continueToNextStepSync);
+ await requestFinished(request);
+
+ info("Getting origin usage");
+
+ request = getOriginUsage(principal, /* fromMemory */ true);
+ await requestFinished(request);
+
+ info("Verifying origin usage");
+
+ is(request.result.usage, originUsage, "Correct origin usage");
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeFromFlatOriginDirectories.js b/dom/quota/test/xpcshell/upgrades/test_upgradeFromFlatOriginDirectories.js
new file mode 100644
index 0000000000..f33ddac8ca
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeFromFlatOriginDirectories.js
@@ -0,0 +1,187 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// A flat origin directory is an origin directory with no sub directories for
+// quota clients. The upgrade was initially done lazily and an empty .metadata
+// file was used to indicate a successful upgrade.
+
+function* testSteps() {
+ const setups = [
+ {
+ packages: [
+ // Storage used prior FF 22 (indexedDB/ directory with flat origin
+ // directories).
+ // FF 26 renamed indexedDB/ to storage/persistent and the lazy upgrade
+ // of flat origin directories remained. There's a test for that below.
+ "indexedDBDirectory_flatOriginDirectories_profile",
+ "../indexedDBDirectory_shared",
+ ],
+ origins: [
+ {
+ oldPath: "indexedDB/1007+f+app+++system.gaiamobile.org",
+ },
+
+ {
+ oldPath: "indexedDB/1007+t+https+++developer.cdn.mozilla.net",
+ },
+
+ {
+ oldPath: "indexedDB/http+++www.mozilla.org",
+ newPath: "storage/default/http+++www.mozilla.org",
+ url: "http://www.mozilla.org",
+ persistence: "default",
+ },
+ ],
+ },
+
+ {
+ packages: [
+ // Storage used by FF 26-35 (storage/persistent/ directory with not yet
+ // upgraded flat origin directories).
+ // FF 36 renamed storage/persistent/ to storage/default/ and all not
+ // yet upgraded flat origin directories were upgraded. There's a
+ // separate test for that:
+ // test_upgradeFromPersistentStorageDirectory_upgradeOriginDirectories.
+ "persistentStorageDirectory_flatOriginDirectories_profile",
+ "../persistentStorageDirectory_shared",
+ ],
+ origins: [
+ {
+ oldPath: "storage/persistent/1007+f+app+++system.gaiamobile.org",
+ },
+
+ {
+ oldPath:
+ "storage/persistent/1007+t+https+++developer.cdn.mozilla.net",
+ },
+
+ {
+ oldPath: "storage/persistent/http+++www.mozilla.org",
+ newPath: "storage/default/http+++www.mozilla.org",
+ url: "http://www.mozilla.org",
+ persistence: "default",
+ },
+ ],
+ },
+ ];
+
+ const metadataFileName = ".metadata";
+
+ for (const setup of setups) {
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying storage");
+
+ verifyStorage(setup.packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(setup.packages);
+
+ info("Verifying storage");
+
+ verifyStorage(setup.packages, "afterInstall");
+
+ info("Checking origin directories");
+
+ for (const origin of setup.origins) {
+ let originDir = getRelativeFile(origin.oldPath);
+ let exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ let idbDir = originDir.clone();
+ idbDir.append("idb");
+
+ exists = idbDir.exists();
+ ok(!exists, "idb directory doesn't exist");
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ exists = metadataFile.exists();
+ ok(!exists, "Metadata file doesn't exist");
+
+ if (origin.newPath) {
+ originDir = getRelativeFile(origin.newPath);
+ exists = originDir.exists();
+ ok(!exists, "Origin directory doesn't exist");
+ }
+ }
+
+ info("Initializing");
+
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Verifying storage");
+
+ verifyStorage(setup.packages, "afterInit");
+
+ // TODO: Remove this block once temporary storage initialization is able to
+ // ignore unknown directories.
+ getRelativeFile("storage/default/invalid+++example.com").remove(false);
+ try {
+ getRelativeFile("storage/temporary/invalid+++example.com").remove(false);
+ } catch (ex) {}
+
+ info("Checking origin directories");
+
+ for (const origin of setup.origins) {
+ let originDir = getRelativeFile(origin.oldPath);
+ let exists = originDir.exists();
+ ok(!exists, "Origin directory doesn't exist");
+
+ if (origin.newPath) {
+ originDir = getRelativeFile(origin.newPath);
+ exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ let idbDir = originDir.clone();
+ idbDir.append("idb");
+
+ exists = idbDir.exists();
+ ok(exists, "idb directory does exist");
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ exists = metadataFile.exists();
+ ok(exists, "Metadata file does exist");
+ }
+ }
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Initializing origins");
+
+ for (const origin of setup.origins) {
+ if (origin.newPath) {
+ info("Initializing origin");
+
+ let principal = getPrincipal(origin.url);
+ request = initTemporaryOrigin(
+ origin.persistence,
+ principal,
+ continueToNextStepSync
+ );
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ ok(!request.result, "Origin directory wasn't created");
+ }
+ }
+ }
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeFromIndexedDBDirectory.js b/dom/quota/test/xpcshell/upgrades/test_upgradeFromIndexedDBDirectory.js
new file mode 100644
index 0000000000..b1b0966d67
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeFromIndexedDBDirectory.js
@@ -0,0 +1,121 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify
+ * MaybeUpgradeFromIndexedDBDirectoryToPersistentStorageDirectory method.
+ */
+
+function* testSteps() {
+ const origins = [
+ {
+ oldPath: "indexedDB/http+++www.mozilla.org",
+ newPath: "storage/default/http+++www.mozilla.org",
+ url: "http://www.mozilla.org",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "indexedDB/1007+f+app+++system.gaiamobile.org",
+ },
+
+ {
+ oldPath: "indexedDB/1007+t+https+++developer.cdn.mozilla.net",
+ },
+ ];
+
+ const packages = [
+ // Storage used prior FF 26 (indexedDB/ directory).
+ "indexedDBDirectory_profile",
+ "../indexedDBDirectory_shared",
+ ];
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing package");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ for (let origin of origins) {
+ let originDir = getRelativeFile(origin.oldPath);
+ let exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ if (origin.newPath) {
+ originDir = getRelativeFile(origin.newPath);
+ exists = originDir.exists();
+ ok(!exists, "Origin directory doesn't exist");
+ }
+ }
+
+ info("Initializing");
+
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ // TODO: Remove this block once temporary storage initialization is able to
+ // ignore unknown directories.
+ getRelativeFile("storage/default/invalid+++example.com").remove(false);
+
+ info("Checking origin directories");
+
+ for (let origin of origins) {
+ let originDir = getRelativeFile(origin.oldPath);
+ let exists = originDir.exists();
+ ok(!exists, "Origin directory doesn't exist");
+
+ if (origin.newPath) {
+ originDir = getRelativeFile(origin.newPath);
+ exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+ }
+ }
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Initializing origins");
+
+ for (const origin of origins) {
+ if (origin.newPath) {
+ info("Initializing origin");
+
+ let principal = getPrincipal(origin.url);
+ request = initTemporaryOrigin(
+ origin.persistence,
+ principal,
+ continueToNextStepSync
+ );
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ ok(!request.result, "Origin directory wasn't created");
+ }
+ }
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeFromIndexedDBDirectory_removeOldDirectory.js b/dom/quota/test/xpcshell/upgrades/test_upgradeFromIndexedDBDirectory_removeOldDirectory.js
new file mode 100644
index 0000000000..a7beced885
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeFromIndexedDBDirectory_removeOldDirectory.js
@@ -0,0 +1,86 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that the old directory is removed in
+ * MaybeUpgradeFromIndexedDBDirectoryToPersistentStorageDirectory method.
+ */
+
+async function testSteps() {
+ const url = "http://www.mozilla.org";
+ const persistence = "default";
+
+ const packages = [
+ // Storage used by FF 26-35 (storage/persistent/ directory and re-created
+ // indexedDB directory by an older FF).
+ "indexedDBAndPersistentStorageDirectory_profile",
+ "../persistentStorageDirectory_shared",
+ ];
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Checking directories");
+
+ let indexedDBDir = getRelativeFile("indexedDB");
+ let exists = indexedDBDir.exists();
+ ok(exists, "IndexedDB directory does exist");
+
+ let persistentStorageDir = getRelativeFile("storage/persistent");
+ exists = persistentStorageDir.exists();
+ ok(exists, "Persistent storage directory does exist");
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ // TODO: Remove this block once temporary storage initialization is able to
+ // ignore unknown directories.
+ getRelativeFile("storage/default/invalid+++example.com").remove(false);
+ getRelativeFile("storage/temporary/invalid+++example.com").remove(false);
+
+ info("Checking directories");
+
+ indexedDBDir = getRelativeFile("indexedDB");
+ exists = indexedDBDir.exists();
+ ok(!exists, "IndexedDB directory doesn't exist");
+
+ // FF 36 renamed storage/persistent/ to storage/default/ so it can't exist
+ // either.
+ persistentStorageDir = getRelativeFile("storage/persistent");
+ exists = persistentStorageDir.exists();
+ ok(!exists, "Persistent storage directory doesn't exist");
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Initializing origin");
+
+ request = initTemporaryOrigin(persistence, getPrincipal(url));
+ await requestFinished(request);
+
+ ok(!request.result, "Origin directory wasn't created");
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory.js b/dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory.js
new file mode 100644
index 0000000000..efac150067
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory.js
@@ -0,0 +1,378 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify
+ * MaybeUpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory method.
+ */
+
+loadScript("dom/quota/test/common/file.js");
+
+function* testSteps() {
+ const origins = [
+ {
+ oldPath: "storage/persistent/1007+f+app+++system.gaiamobile.org",
+ },
+
+ {
+ oldPath: "storage/persistent/1007+t+https+++developer.cdn.mozilla.net",
+ },
+
+ {
+ oldPath: "storage/persistent/chrome",
+ newPath: "storage/permanent/chrome",
+ chrome: true,
+ persistence: "persistent",
+ },
+
+ {
+ oldPath: "storage/persistent/file++++",
+ newPath: "storage/default/file++++",
+ url: "file:///",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/file++++++index.html",
+ newPath: "storage/default/file++++++index.html",
+ url: "file:///+/index.html",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/file++++++index.html",
+ newPath: "storage/default/file++++++index.html",
+ url: "file://///index.html",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/file++++Users+joe+",
+ newPath: "storage/default/file++++Users+joe+",
+ url: "file:///Users/joe/",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/file++++Users+joe+c+++index.html",
+ newPath: "storage/default/file++++Users+joe+c+++index.html",
+ url: "file:///Users/joe/c++/index.html",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/file++++Users+joe+c+++index.html",
+ newPath: "storage/default/file++++Users+joe+c+++index.html",
+ url: "file:///Users/joe/c///index.html",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/file++++Users+joe+index.html",
+ newPath: "storage/default/file++++Users+joe+index.html",
+ url: "file:///Users/joe/index.html",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/file++++c++",
+ newPath: "storage/default/file++++c++",
+ url: "file:///c:/",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/file++++c++Users+joe+",
+ newPath: "storage/default/file++++c++Users+joe+",
+ url: "file:///c:/Users/joe/",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/file++++c++Users+joe+index.html",
+ newPath: "storage/default/file++++c++Users+joe+index.html",
+ url: "file:///c:/Users/joe/index.html",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/http+++127.0.0.1",
+ newPath: "storage/default/http+++127.0.0.1",
+ url: "http://127.0.0.1",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/http+++localhost",
+ newPath: "storage/default/http+++localhost",
+ url: "http://localhost",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/http+++www.mozilla.org",
+ newPath: "storage/default/http+++www.mozilla.org",
+ url: "http://www.mozilla.org",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/http+++www.mozilla.org+8080",
+ newPath: "storage/default/http+++www.mozilla.org+8080",
+ url: "http://www.mozilla.org:8080",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/https+++www.mozilla.org",
+ newPath: "storage/default/https+++www.mozilla.org",
+ url: "https://www.mozilla.org",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/https+++www.mozilla.org+8080",
+ newPath: "storage/default/https+++www.mozilla.org+8080",
+ url: "https://www.mozilla.org:8080",
+ persistence: "default",
+ },
+
+ {
+ oldPath: "storage/persistent/indexeddb+++fx-devtools",
+ newPath: "storage/permanent/indexeddb+++fx-devtools",
+ url: "indexeddb://fx-devtools",
+ persistence: "persistent",
+ },
+
+ {
+ oldPath: "storage/persistent/moz-safe-about+++home",
+ },
+
+ {
+ oldPath: "storage/persistent/moz-safe-about+home",
+ newPath: "storage/permanent/moz-safe-about+home",
+ url: "moz-safe-about:home",
+ persistence: "persistent",
+ },
+
+ {
+ oldPath:
+ "storage/persistent/resource+++fx-share-addon-at-mozilla-dot-org-fx-share-addon-data",
+ newPath:
+ "storage/permanent/resource+++fx-share-addon-at-mozilla-dot-org-fx-share-addon-data",
+ url: "resource://fx-share-addon-at-mozilla-dot-org-fx-share-addon-data",
+ persistence: "persistent",
+ },
+
+ {
+ oldPath: "storage/temporary/1007+f+app+++system.gaiamobile.org",
+ },
+
+ {
+ oldPath: "storage/temporary/1007+t+https+++developer.cdn.mozilla.net",
+ },
+
+ // The .metadata file was intentionally appended for this origin directory
+ // to test recovery from unfinished upgrades (some metadata files can be
+ // already upgraded).
+ {
+ oldPath: "storage/temporary/chrome",
+ newPath: "storage/temporary/chrome",
+ metadataUpgraded: true,
+ chrome: true,
+ persistence: "temporary",
+ },
+
+ {
+ oldPath: "storage/temporary/http+++localhost",
+ newPath: "storage/temporary/http+++localhost",
+ url: "http://localhost",
+ persistence: "temporary",
+ },
+
+ // The .metadata file was intentionally removed for this origin directory
+ // to test restoring during upgrade.
+ {
+ oldPath: "storage/temporary/http+++localhost+81",
+ newPath: "storage/temporary/http+++localhost+81",
+ metadataRemoved: true,
+ url: "http://localhost:81",
+ persistence: "temporary",
+ },
+
+ // The .metadata file was intentionally truncated for this origin directory
+ // to test restoring during upgrade.
+ {
+ oldPath: "storage/temporary/http+++localhost+82",
+ newPath: "storage/temporary/http+++localhost+82",
+ url: "http://localhost:82",
+ persistence: "temporary",
+ },
+ ];
+
+ const metadataFileName = ".metadata";
+
+ const packages = [
+ // Storage used by 26-35 (storage/persistent/ directory, tracked only
+ // timestamp in .metadata for persistent storage and isApp not tracked in
+ // .metadata for temporary storage).
+ "persistentStorageDirectory_profile",
+ "../persistentStorageDirectory_shared",
+ ];
+
+ let metadataBuffers = [];
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Checking origin directories");
+
+ for (let origin of origins) {
+ let originDir = getRelativeFile(origin.oldPath);
+ let exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ if (origin.newPath) {
+ info("Reading out contents of metadata file");
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ if (origin.metadataRemoved) {
+ metadataBuffers.push(new ArrayBuffer(0));
+ } else {
+ File.createFromNsIFile(metadataFile).then(grabArgAndContinueHandler);
+ let file = yield undefined;
+
+ let fileReader = new FileReader();
+ fileReader.onload = continueToNextStepSync;
+ fileReader.readAsArrayBuffer(file);
+
+ yield undefined;
+
+ metadataBuffers.push(fileReader.result);
+ }
+
+ if (origin.newPath != origin.oldPath) {
+ originDir = getRelativeFile(origin.newPath);
+ exists = originDir.exists();
+ ok(!exists, "Origin directory doesn't exist");
+ }
+ }
+ }
+
+ info("Initializing");
+
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ // TODO: Remove this block once temporary storage initialization is able to
+ // ignore unknown directories.
+ getRelativeFile("storage/default/invalid+++example.com").remove(false);
+ getRelativeFile("storage/temporary/invalid+++example.com").remove(false);
+
+ info("Checking origin directories");
+
+ for (let origin of origins) {
+ if (!origin.newPath || origin.newPath != origin.oldPath) {
+ let originDir = getRelativeFile(origin.oldPath);
+ let exists = originDir.exists();
+ ok(!exists, "Origin directory doesn't exist");
+ }
+
+ if (origin.newPath) {
+ let originDir = getRelativeFile(origin.newPath);
+ let exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ info("Reading out contents of metadata file");
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ File.createFromNsIFile(metadataFile).then(grabArgAndContinueHandler);
+ let file = yield undefined;
+
+ let fileReader = new FileReader();
+ fileReader.onload = continueToNextStepSync;
+ fileReader.readAsArrayBuffer(file);
+
+ yield undefined;
+
+ let metadataBuffer = fileReader.result;
+
+ info("Verifying blobs differ");
+
+ if (origin.metadataUpgraded) {
+ ok(
+ compareBuffers(metadataBuffer, metadataBuffers.shift()),
+ "Metadata doesn't differ"
+ );
+ } else {
+ ok(
+ !compareBuffers(metadataBuffer, metadataBuffers.shift()),
+ "Metadata differ"
+ );
+ }
+ }
+ }
+
+ info("Initializing");
+
+ request = initTemporaryStorage(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Initializing origins");
+
+ for (const origin of origins) {
+ if (origin.newPath) {
+ info("Initializing origin");
+
+ let principal;
+ if (origin.chrome) {
+ principal = getCurrentPrincipal();
+ } else {
+ principal = getPrincipal(origin.url);
+ }
+
+ if (origin.persistence == "persistent") {
+ request = initPersistentOrigin(principal, continueToNextStepSync);
+ } else {
+ request = initTemporaryOrigin(
+ origin.persistence,
+ principal,
+ continueToNextStepSync
+ );
+ }
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ ok(!request.result, "Origin directory wasn't created");
+ }
+ }
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory_removeOldDirectory.js b/dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory_removeOldDirectory.js
new file mode 100644
index 0000000000..3f2acadb86
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory_removeOldDirectory.js
@@ -0,0 +1,102 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify that the old directory is removed in
+ * MaybeUpgradeFromPersistentStorageDirectoryToDefaultStorageDirectory method.
+ */
+
+async function testSteps() {
+ const url = "http://www.mozilla.org";
+ const persistence = "default";
+ const lastAccessed = 0x0005330925e07841;
+
+ const packages = [
+ // Storage used by FF 36-48 (storage/default/ directory and re-created
+ // storage/persistent/ directory by an older FF).
+ "persistentAndDefaultStorageDirectory_profile",
+ "../defaultStorageDirectory_shared",
+ ];
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Checking directories");
+
+ let persistentStorageDir = getRelativeFile("storage/persistent");
+ let exists = persistentStorageDir.exists();
+ ok(exists, "Persistent storage directory does exist");
+
+ let defaultStorageDir = getRelativeFile("storage/default");
+ exists = defaultStorageDir.exists();
+ ok(exists, "Default storage directory does exist");
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ // TODO: Remove this block once temporary storage initialization and getting
+ // usage is able to ignore unknown directories.
+ getRelativeFile("storage/default/invalid+++example.com").remove(false);
+ getRelativeFile("storage/permanent/invalid+++example.com").remove(false);
+ getRelativeFile("storage/temporary/invalid+++example.com").remove(false);
+
+ info("Checking directories");
+
+ persistentStorageDir = getRelativeFile("storage/persistent");
+ exists = persistentStorageDir.exists();
+ ok(!exists, "Persistent storage directory doesn't exist");
+
+ defaultStorageDir = getRelativeFile("storage/default");
+ exists = defaultStorageDir.exists();
+ ok(exists, "Default storage directory does exist");
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ info("Initializing origin");
+
+ request = initTemporaryOrigin(persistence, getPrincipal(url));
+ await requestFinished(request);
+
+ ok(!request.result, "Origin directory wasn't created");
+
+ info("Getting usage");
+
+ request = getUsage(function () {}, /* getAll */ true);
+ await requestFinished(request);
+
+ info("Verifying result");
+
+ const result = request.result;
+ is(result.length, 1, "Correct number of usage results");
+
+ info("Verifying usage result");
+
+ const usageResult = result[0];
+ ok(usageResult.origin == url, "Origin equals");
+ ok(usageResult.lastAccessed == lastAccessed, "LastAccessed equals");
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory_upgradeOriginDirectories.js b/dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory_upgradeOriginDirectories.js
new file mode 100644
index 0000000000..cac8ec3b16
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeFromPersistentStorageDirectory_upgradeOriginDirectories.js
@@ -0,0 +1,162 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function* testSteps() {
+ const origins = [
+ {
+ oldPath: "storage/persistent/1007+f+app+++system.gaiamobile.org",
+ upgraded: true,
+ },
+
+ {
+ oldPath: "storage/persistent/1007+t+https+++developer.cdn.mozilla.net",
+ upgraded: true,
+ },
+
+ {
+ oldPath: "storage/persistent/http+++www.mozilla.org",
+ newPath: "storage/default/http+++www.mozilla.org",
+ url: "http://www.mozilla.org",
+ persistence: "default",
+ upgraded: true,
+ },
+ {
+ oldPath: "storage/persistent/http+++www.mozilla.org+8080",
+ newPath: "storage/default/http+++www.mozilla.org+8080",
+ url: "http://www.mozilla.org:8080",
+ persistence: "default",
+ },
+ ];
+
+ const metadataFileName = ".metadata";
+
+ const packages = [
+ // Storage used by FF 26-35 (storage/persistent/ directory with already
+ // upgraded origin directories and not yet upgraded flat origin
+ // directories).
+ "persistentStorageDirectory_originDirectories_profile",
+ "../persistentStorageDirectory_shared",
+ ];
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Checking origin directories");
+
+ for (const origin of origins) {
+ let originDir = getRelativeFile(origin.oldPath);
+ let exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ let idbDir = originDir.clone();
+ idbDir.append("idb");
+
+ exists = idbDir.exists();
+ if (origin.upgraded) {
+ ok(exists, "idb directory does exist");
+ } else {
+ ok(!exists, "idb directory doesn't exist");
+ }
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ exists = metadataFile.exists();
+ if (origin.upgraded) {
+ ok(exists, "Metadata file does exist");
+ } else {
+ ok(!exists, "Metadata file doesn't exist");
+ }
+
+ if (origin.newPath) {
+ originDir = getRelativeFile(origin.newPath);
+ exists = originDir.exists();
+ ok(!exists, "Origin directory doesn't exist");
+ }
+ }
+
+ info("Initializing");
+
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ // TODO: Remove this block once temporary storage initialization and getting
+ // usage is able to ignore unknown directories.
+ getRelativeFile("storage/default/invalid+++example.com").remove(false);
+ getRelativeFile("storage/temporary/invalid+++example.com").remove(false);
+
+ info("Checking origin directories");
+
+ for (const origin of origins) {
+ let originDir = getRelativeFile(origin.oldPath);
+ let exists = originDir.exists();
+ ok(!exists, "Origin directory doesn't exist");
+
+ if (origin.newPath) {
+ originDir = getRelativeFile(origin.newPath);
+ exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ let idbDir = originDir.clone();
+ idbDir.append("idb");
+
+ exists = idbDir.exists();
+ ok(exists, "idb directory does exist");
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ exists = metadataFile.exists();
+ ok(exists, "Metadata file does exist");
+ }
+ }
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Initializing origins");
+
+ for (const origin of origins) {
+ if (origin.newPath) {
+ info("Initializing origin");
+
+ let principal = getPrincipal(origin.url);
+ request = initTemporaryOrigin(
+ origin.persistence,
+ principal,
+ continueToNextStepSync
+ );
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ ok(!request.result, "Origin directory wasn't created");
+ }
+ }
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom0_0.js b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom0_0.js
new file mode 100644
index 0000000000..bd6a010cf8
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom0_0.js
@@ -0,0 +1,158 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify UpgradeStorageFrom0_0To1_0 method.
+ */
+
+function* testSteps() {
+ const origins = [
+ {
+ path: "storage/default/1007+f+app+++system.gaiamobile.org",
+ obsolete: true,
+ },
+
+ {
+ path: "storage/default/1007+t+https+++developer.cdn.mozilla.net",
+ obsolete: true,
+ },
+
+ {
+ path: "storage/default/http+++www.mozilla.org",
+ obsolete: false,
+ url: "http://www.mozilla.org",
+ persistence: "default",
+ },
+ ];
+
+ const storageFileName = "storage.sqlite";
+ const metadataFileName = ".metadata";
+ const metadata2FileName = ".metadata-v2";
+
+ const packages = [
+ // Storage used by FF 36-48 (storage/default/ directory, but no
+ // storage.sqlite and no .metadata-v2 files).
+ "version0_0_profile",
+ "../defaultStorageDirectory_shared",
+ ];
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Checking storage file");
+
+ let storageFile = getRelativeFile(storageFileName);
+
+ let exists = storageFile.exists();
+ ok(!exists, "Storage file doesn't exist");
+
+ info("Checking origin directories");
+
+ for (let origin of origins) {
+ let originDir = getRelativeFile(origin.path);
+
+ exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ exists = metadataFile.exists();
+ ok(exists, "Metadata file does exist");
+
+ let metadata2File = originDir.clone();
+ metadata2File.append(metadata2FileName);
+
+ exists = metadata2File.exists();
+ ok(!exists, "Metadata file doesn't exist");
+ }
+
+ info("Initializing");
+
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ // TODO: Remove this block once temporary storage initialization is able to
+ // ignore unknown directories.
+ getRelativeFile("storage/default/invalid+++example.com").remove(false);
+ getRelativeFile("storage/temporary/invalid+++example.com").remove(false);
+
+ exists = storageFile.exists();
+ ok(exists, "Storage file does exist");
+
+ info("Checking origin directories");
+
+ for (let origin of origins) {
+ let originDir = getRelativeFile(origin.path);
+
+ exists = originDir.exists();
+ if (origin.obsolete) {
+ ok(!exists, "Origin directory doesn't exist");
+ } else {
+ ok(exists, "Origin directory does exist");
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ exists = metadataFile.exists();
+ ok(exists, "Metadata file does exist");
+
+ let metadata2File = originDir.clone();
+ metadata2File.append(metadata2FileName);
+
+ exists = metadata2File.exists();
+ ok(exists, "Metadata file does exist");
+ }
+ }
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Initializing origins");
+
+ for (const origin of origins) {
+ if (!origin.obsolete) {
+ info("Initializing origin");
+
+ let principal = getPrincipal(origin.url);
+ request = initTemporaryOrigin(
+ origin.persistence,
+ principal,
+ continueToNextStepSync
+ );
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ ok(!request.result, "Origin directory wasn't created");
+ }
+ }
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_idb.js b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_idb.js
new file mode 100644
index 0000000000..34508f85e8
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_idb.js
@@ -0,0 +1,43 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify indexedDB::QuotaClient::UpgradeStorageFrom1_0To2_0
+ * method.
+ */
+
+async function testSteps() {
+ const packages = [
+ // Storage used by FF 49-54 (storage version 1.0 with idb directory).
+ "version1_0_idb_profile",
+ "../defaultStorageDirectory_shared",
+ ];
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Initializing");
+
+ request = init();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_removeAppsData.js b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_removeAppsData.js
new file mode 100644
index 0000000000..08674b3065
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_removeAppsData.js
@@ -0,0 +1,100 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify MaybeRemoveAppsData method.
+ */
+
+function* testSteps() {
+ const origins = [
+ {
+ path: "storage/default/http+++www.mozilla.org",
+ obsolete: false,
+ },
+
+ {
+ path: "storage/default/app+++system.gaiamobile.org^appId=1007",
+ obsolete: true,
+ },
+
+ {
+ path: "storage/default/https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1",
+ obsolete: true,
+ },
+ ];
+
+ const packages = [
+ // Storage used by FF 49-54 (storage version 1.0 with apps data).
+ "version1_0_appsData_profile",
+ "../defaultStorageDirectory_shared",
+ ];
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Checking origin directories");
+
+ for (let origin of origins) {
+ let originDir = getRelativeFile(origin.path);
+
+ let exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+ }
+
+ info("Initializing");
+
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ // TODO: Remove this block once getting usage is able to ignore unknown
+ // directories.
+ getRelativeFile("storage/default/invalid+++example.com").remove(false);
+ getRelativeFile("storage/permanent/invalid+++example.com").remove(false);
+ getRelativeFile("storage/temporary/invalid+++example.com").remove(false);
+
+ info("Checking origin directories");
+
+ for (let origin of origins) {
+ let originDir = getRelativeFile(origin.path);
+
+ let exists = originDir.exists();
+ if (origin.obsolete) {
+ ok(!exists, "Origin directory doesn't exist");
+ } else {
+ ok(exists, "Origin directory does exist");
+ }
+ }
+
+ info("Getting usage");
+
+ getUsage(grabResultAndContinueHandler, /* getAll */ true);
+ let result = yield undefined;
+
+ info("Verifying result");
+
+ is(result.length, 1, "Correct number of usage results");
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_removeMorgueDirectory.js b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_removeMorgueDirectory.js
new file mode 100644
index 0000000000..b37d00bbf8
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_removeMorgueDirectory.js
@@ -0,0 +1,60 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify MaybeRemoveMorgueDirectory method.
+ */
+
+function* testSteps() {
+ const morgueFile = "storage/default/http+++example.com/morgue";
+
+ const packages = [
+ // Storage used by FF 49-54 (storage version 1.0 with morgue directory).
+ "version1_0_morgueDirectory_profile",
+ "../defaultStorageDirectory_shared",
+ ];
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Checking morgue file");
+
+ let file = getRelativeFile(morgueFile);
+
+ let exists = file.exists();
+ ok(exists, "Morgue file does exist");
+
+ info("Initializing");
+
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ info("Checking morgue file");
+
+ exists = file.exists();
+ ok(!exists, "Morgue file doesn't exist");
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_stripObsoleteOriginAttributes.js b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_stripObsoleteOriginAttributes.js
new file mode 100644
index 0000000000..407a04c1f8
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_stripObsoleteOriginAttributes.js
@@ -0,0 +1,179 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify MaybeStripObsoleteOriginAttributes method.
+ */
+
+loadScript("dom/quota/test/common/file.js");
+
+function* testSteps() {
+ const origins = [
+ {
+ oldPath:
+ "storage/permanent/moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f^addonId=indexedDB-test%40kmaglione.mozilla.com",
+ newPath:
+ "storage/permanent/moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f",
+ url: "moz-extension://8ea6d31b-917c-431f-a204-15b95e904d4f",
+ persistence: "persistent",
+ },
+
+ {
+ oldPath:
+ "storage/temporary/moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f^addonId=indexedDB-test%40kmaglione.mozilla.com",
+ newPath:
+ "storage/temporary/moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f",
+ url: "moz-extension://8ea6d31b-917c-431f-a204-15b95e904d4f",
+ persistence: "temporary",
+ },
+
+ {
+ oldPath:
+ "storage/default/moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f^addonId=indexedDB-test%40kmaglione.mozilla.com",
+ newPath:
+ "storage/default/moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f",
+ url: "moz-extension://8ea6d31b-917c-431f-a204-15b95e904d4f",
+ persistence: "default",
+ },
+ ];
+
+ const metadataFileName = ".metadata-v2";
+
+ const packages = [
+ // Storage used by FF 49-54 (storage version 1.0 with obsolete origin
+ // attributes).
+ "version1_0_obsoleteOriginAttributes_profile",
+ "../defaultStorageDirectory_shared",
+ ];
+
+ let metadataBuffers = [];
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Checking origin directories");
+
+ for (let origin of origins) {
+ let originDir = getRelativeFile(origin.oldPath);
+ let exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ info("Reading out contents of metadata file");
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ File.createFromNsIFile(metadataFile).then(grabArgAndContinueHandler);
+ let file = yield undefined;
+
+ let fileReader = new FileReader();
+ fileReader.onload = continueToNextStepSync;
+ fileReader.readAsArrayBuffer(file);
+
+ yield undefined;
+
+ metadataBuffers.push(fileReader.result);
+
+ originDir = getRelativeFile(origin.newPath);
+ exists = originDir.exists();
+ ok(!exists, "Origin directory doesn't exist");
+ }
+
+ info("Initializing");
+
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ // TODO: Remove this block once temporary storage initialization is able to
+ // ignore unknown directories.
+ getRelativeFile("storage/default/invalid+++example.com").remove(false);
+ getRelativeFile("storage/temporary/invalid+++example.com").remove(false);
+
+ info("Checking origin directories");
+
+ for (let origin of origins) {
+ let originDir = getRelativeFile(origin.oldPath);
+ let exists = originDir.exists();
+ ok(!exists, "Origin directory doesn't exist");
+
+ originDir = getRelativeFile(origin.newPath);
+ exists = originDir.exists();
+ ok(exists, "Origin directory does exist");
+
+ info("Reading out contents of metadata file");
+
+ let metadataFile = originDir.clone();
+ metadataFile.append(metadataFileName);
+
+ File.createFromNsIFile(metadataFile).then(grabArgAndContinueHandler);
+ let file = yield undefined;
+
+ let fileReader = new FileReader();
+ fileReader.onload = continueToNextStepSync;
+ fileReader.readAsArrayBuffer(file);
+
+ yield undefined;
+
+ let metadataBuffer = fileReader.result;
+
+ info("Verifying blobs differ");
+
+ ok(
+ !compareBuffers(metadataBuffer, metadataBuffers.shift()),
+ "Metadata differ"
+ );
+ }
+
+ info("Initializing temporary storage");
+
+ request = initTemporaryStorage(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Initializing origins");
+
+ for (const origin of origins) {
+ info("Initializing origin");
+
+ let principal = getPrincipal(origin.url);
+ if (origin.persistence == "persistent") {
+ request = initPersistentOrigin(principal, continueToNextStepSync);
+ } else {
+ request = initTemporaryOrigin(
+ origin.persistence,
+ principal,
+ continueToNextStepSync
+ );
+ }
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ ok(!request.result, "Origin directory wasn't created");
+ }
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_0.js b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_0.js
new file mode 100644
index 0000000000..55edfa6055
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_0.js
@@ -0,0 +1,97 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify UpgradeStorageFrom2_0To2_1 method.
+ */
+
+function* testSteps() {
+ const origins = [
+ "storage/default/chrome/",
+ "storage/default/http+++www.mozilla.org/",
+ ];
+ const paddingFilePath = "cache/.padding";
+
+ const packages = [
+ // Storage used by FF 55-56 (storage version 2.0).
+ // The profile contains two cache storages:
+ // - storage/default/chrome/cache,
+ // - storage/default/http+++www.mozilla.org/cache
+ // The file create_cache.js in the package was run locally, specifically it
+ // was temporarily added to xpcshell.ini and then executed:
+ // mach xpcshell-test --interactive dom/quota/test/xpcshell/create_cache.js
+ // Note: it only creates the directory "storage/default/chrome/cache".
+ // To make it become the profile in the test, two more manual steps are
+ // needed.
+ // 1. Remove the folder "storage/temporary".
+ // 2. Copy the content under the "storage/default/chrome" to
+ // "storage/default/http+++www.mozilla.org".
+ // 3. Manually create an asmjs folder under the
+ // "storage/default/http+++www.mozilla.org/".
+ "version2_0_profile",
+ "../defaultStorageDirectory_shared",
+ ];
+
+ info("Clearing");
+
+ clear(continueToNextStepSync);
+ yield undefined;
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Checking padding files before upgrade (storage version 2.0)");
+
+ for (let origin of origins) {
+ let paddingFile = getRelativeFile(origin + paddingFilePath);
+ let exists = paddingFile.exists();
+ ok(!exists, "Padding file doesn't exist");
+ }
+
+ info("Initializing");
+
+ // Initialize to trigger storage upgrade from version 2.0.
+ let request = init(continueToNextStepSync);
+ yield undefined;
+
+ ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ info("Checking padding files after upgrade");
+
+ for (let origin of origins) {
+ let paddingFile = getRelativeFile(origin + paddingFilePath);
+ let exists = paddingFile.exists();
+ ok(exists, "Padding file does exist");
+
+ info("Reading out contents of padding file");
+
+ File.createFromNsIFile(paddingFile).then(grabArgAndContinueHandler);
+ let domFile = yield undefined;
+
+ let fileReader = new FileReader();
+ fileReader.onload = continueToNextStepSync;
+ fileReader.readAsArrayBuffer(domFile);
+ yield undefined;
+
+ let paddingFileInfo = new Float64Array(fileReader.result);
+ ok(paddingFileInfo.length == 1, "Padding file does take 64 bytes.");
+ ok(paddingFileInfo[0] == 0, "Padding size does default to zero.");
+ }
+
+ finishTest();
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_1.js b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_1.js
new file mode 100644
index 0000000000..eed7ed21d5
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_1.js
@@ -0,0 +1,85 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify UpgradeStorageFrom2_1To2_2 method (removal of
+ * obsolete origins, deprecated clients and unknown temporary files).
+ */
+
+async function testSteps() {
+ const filePaths = [
+ // Obsolete origins:
+ "storage/default/chrome+++content+browser.xul",
+
+ "storage/default/moz-safe-about+++home",
+
+ // TODO: These three origins don't belong here! They were added one release
+ // later and the origin parser was fixed to handle these origins one
+ // release later as well, so users which already upgraded to 2.2 may
+ // still have issues related to these origins!
+ "storage/default/about+home+1",
+
+ "storage/default/about+home+1+q",
+
+ // about:reader?url=xxx (before bug 1422456)
+ "storage/default/about+reader+url=https%3A%2F%2Fexample.com",
+
+ // Deprecated client:
+ "storage/default/https+++example.com/asmjs",
+
+ // Unknown temporary file:
+ "storage/default/https+++example.com/idb/UUID123.tmp",
+ ];
+
+ const packages = [
+ // Storage used by FF 57-67 (storage version 2.1 with obsolete origins, a
+ // deprecated client and an unknown temporary file).
+ "version2_1_profile",
+ "../defaultStorageDirectory_shared",
+ ];
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ info("Checking files and directories before upgrade (storage version 2.1)");
+
+ for (const filePath of filePaths) {
+ let file = getRelativeFile(filePath);
+ let exists = file.exists();
+ ok(exists, "File or directory does exist");
+ }
+
+ info("Initializing");
+
+ // Initialize to trigger storage upgrade from version 2.1
+ request = init();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ info("Checking files and directories after upgrade");
+
+ for (const filePath of filePaths) {
+ let file = getRelativeFile(filePath);
+ let exists = file.exists();
+ ok(!exists, "File or directory does not exist");
+ }
+}
diff --git a/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_2.js b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_2.js
new file mode 100644
index 0000000000..8f41c05b49
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom2_2.js
@@ -0,0 +1,64 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is mainly to verify UpgradeStorageFrom2_2To2_3 method.
+ */
+
+async function testSteps() {
+ const packages = [
+ // Storage used by FF 68-69 (storage version 2.2).
+ "version2_2_profile",
+ "../defaultStorageDirectory_shared",
+ ];
+
+ function verifyDatabaseTable(shouldExist) {
+ let file = getRelativeFile("storage.sqlite");
+ let conn = Services.storage.openUnsharedDatabase(file);
+
+ let exists = conn.tableExists("database");
+ if (shouldExist) {
+ ok(exists, "Database table does exist");
+ } else {
+ ok(!exists, "Database table does not exist");
+ }
+
+ conn.close();
+ }
+
+ info("Clearing");
+
+ let request = clear();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "beforeInstall");
+
+ info("Installing packages");
+
+ installPackages(packages);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInstall");
+
+ verifyDatabaseTable(/* shouldExist */ false);
+
+ info("Initializing");
+
+ // Initialize to trigger storage upgrade from version 2.2
+ request = init();
+ await requestFinished(request);
+
+ info("Verifying storage");
+
+ verifyStorage(packages, "afterInit");
+
+ request = reset();
+ await requestFinished(request);
+
+ verifyDatabaseTable(/* shouldExist */ true);
+}
diff --git a/dom/quota/test/xpcshell/upgrades/version0_0_profile.json b/dom/quota/test/xpcshell/upgrades/version0_0_profile.json
new file mode 100644
index 0000000000..05c0d27fe3
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version0_0_profile.json
@@ -0,0 +1,88 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "1007+f+app+++system.gaiamobile.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "1007+t+https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "1007+t+https+++developer.cdn.mozilla.org",
+ "dir": true,
+ "entries": [{ "name": ".metadata", "dir": false }]
+ },
+ {
+ "name": "https+++developer.cdn.mozilla.org",
+ "dir": true
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "todo": "This shouldn't exist, it regressed after accidental changes done in bug 1320404",
+ "name": "https+++developer.cdn.mozilla.net",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "https+++developer.cdn.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/version0_0_profile.zip b/dom/quota/test/xpcshell/upgrades/version0_0_profile.zip
new file mode 100644
index 0000000000..5ef577191c
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version0_0_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/version1_0_appsData_profile.json b/dom/quota/test/xpcshell/upgrades/version1_0_appsData_profile.json
new file mode 100644
index 0000000000..88f5d5dcee
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version1_0_appsData_profile.json
@@ -0,0 +1,72 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "app+++system.gaiamobile.org^appId=1007",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/version1_0_appsData_profile.zip b/dom/quota/test/xpcshell/upgrades/version1_0_appsData_profile.zip
new file mode 100644
index 0000000000..582edb43af
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version1_0_appsData_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/version1_0_idb_profile.json b/dom/quota/test/xpcshell/upgrades/version1_0_idb_profile.json
new file mode 100644
index 0000000000..9f3b53f57d
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version1_0_idb_profile.json
@@ -0,0 +1,73 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "moz-safe-about+home",
+ "dir": true,
+ "entries": [
+ {
+ "name": "idb",
+ "dir": true,
+ "entries": [
+ { "name": "631132235dGb", "dir": true },
+ { "name": "631132235dGb.files", "dir": true },
+ { "name": "631132235dGb.sqlite", "dir": false }
+ ]
+ },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "moz-safe-about+home",
+ "dir": true,
+ "entries": [
+ {
+ "name": "idb",
+ "dir": true,
+ "entries": [
+ { "name": "631132235dGb.files", "dir": true },
+ { "name": "631132235dGb.sqlite", "dir": false }
+ ]
+ },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/version1_0_idb_profile.zip b/dom/quota/test/xpcshell/upgrades/version1_0_idb_profile.zip
new file mode 100644
index 0000000000..8abfae79c2
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version1_0_idb_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/version1_0_morgueDirectory_profile.json b/dom/quota/test/xpcshell/upgrades/version1_0_morgueDirectory_profile.json
new file mode 100644
index 0000000000..855f7846bc
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version1_0_morgueDirectory_profile.json
@@ -0,0 +1,57 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++example.com",
+ "dir": true,
+ "entries": [
+ { "name": "morgue", "dir": true },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "http+++example.com",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/version1_0_morgueDirectory_profile.zip b/dom/quota/test/xpcshell/upgrades/version1_0_morgueDirectory_profile.zip
new file mode 100644
index 0000000000..88543784ec
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version1_0_morgueDirectory_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/version1_0_obsoleteOriginAttributes_profile.json b/dom/quota/test/xpcshell/upgrades/version1_0_obsoleteOriginAttributes_profile.json
new file mode 100644
index 0000000000..071c4413f4
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version1_0_obsoleteOriginAttributes_profile.json
@@ -0,0 +1,112 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f^addonId=indexedDB-test%40kmaglione.mozilla.com",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f^addonId=indexedDB-test%40kmaglione.mozilla.com",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ {
+ "name": "moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f^addonId=indexedDB-test%40kmaglione.mozilla.com",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "permanent",
+ "dir": true,
+ "entries": [
+ {
+ "name": "moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ {
+ "name": "temporary",
+ "dir": true,
+ "entries": [
+ {
+ "name": "moz-extension+++8ea6d31b-917c-431f-a204-15b95e904d4f",
+ "dir": true,
+ "entries": [
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/version1_0_obsoleteOriginAttributes_profile.zip b/dom/quota/test/xpcshell/upgrades/version1_0_obsoleteOriginAttributes_profile.zip
new file mode 100644
index 0000000000..2b4125edf9
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version1_0_obsoleteOriginAttributes_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/version2_0_profile.json b/dom/quota/test/xpcshell/upgrades/version2_0_profile.json
new file mode 100644
index 0000000000..04ad73eae3
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version2_0_profile.json
@@ -0,0 +1,105 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "chrome",
+ "dir": true,
+ "entries": [
+ {
+ "name": "cache",
+ "dir": true,
+ "entries": [
+ { "name": "morgue", "dir": true },
+ { "name": "caches.sqlite", "dir": false }
+ ]
+ },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ { "name": "asmjs", "dir": true },
+ {
+ "name": "cache",
+ "dir": true,
+ "entries": [
+ { "name": "morgue", "dir": true },
+ { "name": "caches.sqlite", "dir": false }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "chrome",
+ "dir": true,
+ "entries": [
+ {
+ "name": "cache",
+ "dir": true,
+ "entries": [
+ { "name": "morgue", "dir": true },
+ { "name": ".padding", "dir": false },
+ { "name": "caches.sqlite", "dir": false }
+ ]
+ },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ },
+ {
+ "name": "http+++www.mozilla.org",
+ "dir": true,
+ "entries": [
+ {
+ "name": "cache",
+ "dir": true,
+ "entries": [
+ { "name": "morgue", "dir": true },
+ { "name": ".padding", "dir": false },
+ { "name": "caches.sqlite", "dir": false }
+ ]
+ },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/version2_0_profile.zip b/dom/quota/test/xpcshell/upgrades/version2_0_profile.zip
new file mode 100644
index 0000000000..c140df56e4
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version2_0_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/version2_1_profile.json b/dom/quota/test/xpcshell/upgrades/version2_1_profile.json
new file mode 100644
index 0000000000..a7866d1123
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version2_1_profile.json
@@ -0,0 +1,69 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ { "name": "about+home+1", "dir": true },
+ { "name": "about+home+1+q", "dir": true },
+ {
+ "name": "about+reader+url=https%3A%2F%2Fexample.com",
+ "dir": true
+ },
+ { "name": "chrome+++content+browser.xul", "dir": true },
+ {
+ "name": "https+++example.com",
+ "dir": true,
+ "entries": [
+ { "name": "asmjs", "dir": true },
+ {
+ "name": "idb",
+ "dir": true,
+ "entries": [{ "name": "UUID123.tmp", "dir": false }]
+ }
+ ]
+ },
+ { "name": "moz-safe-about+++home", "dir": true }
+ ]
+ }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [
+ {
+ "name": "default",
+ "dir": true,
+ "entries": [
+ {
+ "name": "https+++example.com",
+ "dir": true,
+ "entries": [
+ { "name": "idb", "dir": true },
+ { "name": ".metadata", "dir": false },
+ { "name": ".metadata-v2", "dir": false }
+ ]
+ }
+ ]
+ },
+ { "name": "ls-archive.sqlite", "dir": false }
+ ]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/version2_1_profile.zip b/dom/quota/test/xpcshell/upgrades/version2_1_profile.zip
new file mode 100644
index 0000000000..908dac7058
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version2_1_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/version2_2_profile.json b/dom/quota/test/xpcshell/upgrades/version2_2_profile.json
new file mode 100644
index 0000000000..4b7265e3b4
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version2_2_profile.json
@@ -0,0 +1,18 @@
+[
+ { "key": "beforeInstall", "entries": [] },
+ {
+ "key": "afterInstall",
+ "entries": [{ "name": "storage.sqlite", "dir": false }]
+ },
+ {
+ "key": "afterInit",
+ "entries": [
+ {
+ "name": "storage",
+ "dir": true,
+ "entries": [{ "name": "ls-archive.sqlite", "dir": false }]
+ },
+ { "name": "storage.sqlite", "dir": false }
+ ]
+ }
+]
diff --git a/dom/quota/test/xpcshell/upgrades/version2_2_profile.zip b/dom/quota/test/xpcshell/upgrades/version2_2_profile.zip
new file mode 100644
index 0000000000..b6ae7e7d76
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/version2_2_profile.zip
Binary files differ
diff --git a/dom/quota/test/xpcshell/upgrades/xpcshell.ini b/dom/quota/test/xpcshell/upgrades/xpcshell.ini
new file mode 100644
index 0000000000..8698e4bff4
--- /dev/null
+++ b/dom/quota/test/xpcshell/upgrades/xpcshell.ini
@@ -0,0 +1,61 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+head = head.js
+support-files =
+ cacheVersion1_profile.json
+ cacheVersion1_profile.zip
+ indexedDBAndPersistentStorageDirectory_profile.json
+ indexedDBAndPersistentStorageDirectory_profile.zip
+ indexedDBDirectory_flatOriginDirectories_profile.json
+ indexedDBDirectory_flatOriginDirectories_profile.zip
+ indexedDBDirectory_profile.json
+ indexedDBDirectory_profile.zip
+ localStorageArchive1upgrade_profile.zip
+ localStorageArchive4upgrade_profile.zip
+ localStorageArchiveDowngrade_profile.zip
+ persistentAndDefaultStorageDirectory_profile.json
+ persistentAndDefaultStorageDirectory_profile.zip
+ persistentStorageDirectory_flatOriginDirectories_profile.json
+ persistentStorageDirectory_flatOriginDirectories_profile.zip
+ persistentStorageDirectory_originDirectories_profile.json
+ persistentStorageDirectory_originDirectories_profile.zip
+ persistentStorageDirectory_profile.json
+ persistentStorageDirectory_profile.zip
+ version0_0_profile.json
+ version0_0_profile.zip
+ version1_0_appsData_profile.json
+ version1_0_appsData_profile.zip
+ version1_0_idb_profile.json
+ version1_0_idb_profile.zip
+ version1_0_morgueDirectory_profile.json
+ version1_0_morgueDirectory_profile.zip
+ version1_0_obsoleteOriginAttributes_profile.json
+ version1_0_obsoleteOriginAttributes_profile.zip
+ version2_0_profile.json
+ version2_0_profile.zip
+ version2_1_profile.json
+ version2_1_profile.zip
+ version2_2_profile.json
+ version2_2_profile.zip
+
+[test_localStorageArchive1upgrade.js]
+[test_localStorageArchive4upgrade.js]
+[test_localStorageArchiveDowngrade.js]
+[test_upgradeCacheFrom1.js]
+[test_upgradeFromFlatOriginDirectories.js]
+[test_upgradeFromIndexedDBDirectory.js]
+[test_upgradeFromIndexedDBDirectory_removeOldDirectory.js]
+[test_upgradeFromPersistentStorageDirectory.js]
+[test_upgradeFromPersistentStorageDirectory_removeOldDirectory.js]
+[test_upgradeFromPersistentStorageDirectory_upgradeOriginDirectories.js]
+[test_upgradeStorageFrom0_0.js]
+[test_upgradeStorageFrom1_0_idb.js]
+[test_upgradeStorageFrom1_0_removeAppsData.js]
+[test_upgradeStorageFrom1_0_removeMorgueDirectory.js]
+[test_upgradeStorageFrom1_0_stripObsoleteOriginAttributes.js]
+[test_upgradeStorageFrom2_0.js]
+[test_upgradeStorageFrom2_1.js]
+[test_upgradeStorageFrom2_2.js]
diff --git a/dom/quota/test/xpcshell/xpcshell.ini b/dom/quota/test/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..3fb330ea60
--- /dev/null
+++ b/dom/quota/test/xpcshell/xpcshell.ini
@@ -0,0 +1,65 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+head = head.js
+tags = condprof
+support-files =
+ basics_profile.zip
+ clearStoragesForPrincipal_profile.zip
+ clearStoragesForPrivateBrowsing_profile.json
+ clearStoragesForPrivateBrowsing_profile.zip
+ createLocalStorage_profile.zip
+ defaultStorageDirectory_shared.json
+ defaultStorageDirectory_shared.zip
+ getUsage_profile.zip
+ groupMismatch_profile.zip
+ indexedDBDirectory_shared.json
+ indexedDBDirectory_shared.zip
+ originMismatch_profile.json
+ originMismatch_profile.zip
+ persistentStorageDirectory_shared.json
+ persistentStorageDirectory_shared.zip
+ removeLocalStorage1_profile.zip
+ removeLocalStorage2_profile.zip
+ tempMetadataCleanup_profile.zip
+ unknownFiles_profile.zip
+
+[make_unknownFiles.js]
+skip-if = true # Only used for recreating unknownFiles_profile.zip
+[make_unsetLastAccessTime.js]
+skip-if = true # Only used for recreating unsetLastAccessTime_profile.zip
+[test_allowListFiles.js]
+[test_basics.js]
+[test_bad_origin_directory.js]
+[test_createLocalStorage.js]
+[test_clearStoragesForPrincipal.js]
+[test_clearStoragesForPrivateBrowsing.js]
+[test_clearStoragesForOriginAttributesPattern.js]
+[test_estimateOrigin.js]
+[test_getUsage.js]
+[test_groupMismatch.js]
+skip-if = true # The group is now always empty, so metadata can't differ anymore.
+[test_initTemporaryStorage.js]
+[test_listOrigins.js]
+[test_originEndsWithDot.js]
+[test_originMismatch.js]
+[test_originWithCaret.js]
+[test_orpahnedQuotaObject.js]
+[test_persist.js]
+[test_persist_eviction.js]
+[test_persist_globalLimit.js]
+[test_persist_groupLimit.js]
+[test_removeLocalStorage.js]
+[test_simpledb.js]
+[test_specialOrigins.js]
+[test_storagePressure.js]
+skip-if = condprof
+[test_tempMetadataCleanup.js]
+[test_unaccessedOrigins.js]
+[test_unknownFiles.js]
+[test_unsetLastAccessTime.js]
+support-files =
+ unsetLastAccessTime_profile.zip
+[test_validOrigins.js]