diff options
Diffstat (limited to 'dom/quota/test')
195 files changed, 15234 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..6dcf32ff67 --- /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..4ddcfada96 --- /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.loadURI(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.loadURI(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..19ee8ef499 --- /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.loadURI(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.loadURI(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.loadURI(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.loadURI(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..31c7e588a5 --- /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.loadURI(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..fd81168b02 --- /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..aae80adcd1 --- /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..d3927d649a --- /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..bb0f3669be --- /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..25acbe9384 --- /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..b5e64f2cf1 --- /dev/null +++ b/dom/quota/test/gtest/TestEncryptedStream.cpp @@ -0,0 +1,786 @@ +/* -*- 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::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..b62f15363e --- /dev/null +++ b/dom/quota/test/gtest/TestFileOutputStream.cpp @@ -0,0 +1,182 @@ +/* -*- 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, + 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->GetDirectoryForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, originMetadata.mOrigin); + + 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..4be2e131c2 --- /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..09cd6e66c8 --- /dev/null +++ b/dom/quota/test/mochitest/mochitest.ini @@ -0,0 +1,16 @@ +# 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] +[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..03499166b5 --- /dev/null +++ b/dom/quota/test/modules/content/.eslintrc.js @@ -0,0 +1,22 @@ +/** + * 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", + "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..a47061f751 --- /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..b29f7d9c26 --- /dev/null +++ b/dom/quota/test/modules/content/StorageUtils.js @@ -0,0 +1,48 @@ +/** + * 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 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/WorkerDriver.js b/dom/quota/test/modules/content/WorkerDriver.js new file mode 100644 index 0000000000..365d00f363 --- /dev/null +++ b/dom/quota/test/modules/content/WorkerDriver.js @@ -0,0 +1,53 @@ +/** + * 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 + ); + + const worker = new Worker(globalHeadUrl.href); + + worker.onmessage = function(event) { + const data = event.data; + + 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..470c23b2fa --- /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"], + 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..23ee7c06df --- /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/head.js b/dom/quota/test/modules/content/worker/head.js new file mode 100644 index 0000000000..58d4591e47 --- /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..7bddfd2760 --- /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..bda40a4747 --- /dev/null +++ b/dom/quota/test/modules/system/StorageUtils.sys.mjs @@ -0,0 +1,65 @@ +/** + * 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 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; +} 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..c03a94cf56 --- /dev/null +++ b/dom/quota/test/modules/system/WorkerDriver.sys.mjs @@ -0,0 +1,52 @@ +/** + * 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" + ); + + const worker = new Worker(globalHeadUrl.href); + + worker.onmessage = function(event) { + const data = event.data; + + 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..6de1fbc299 --- /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/head.js b/dom/quota/test/modules/system/worker/head.js new file mode 100644 index 0000000000..1308508918 --- /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..bab65f643e --- /dev/null +++ b/dom/quota/test/moz.build @@ -0,0 +1,76 @@ +# -*- 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/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", +] + +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/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", +] diff --git a/dom/quota/test/xpcshell/basics_profile.zip b/dom/quota/test/xpcshell/basics_profile.zip Binary files differnew file mode 100644 index 0000000000..bbdb0f50cf --- /dev/null +++ b/dom/quota/test/xpcshell/basics_profile.zip diff --git a/dom/quota/test/xpcshell/caching/groupMismatch_profile.zip b/dom/quota/test/xpcshell/caching/groupMismatch_profile.zip Binary files differnew file mode 100644 index 0000000000..8124e589de --- /dev/null +++ b/dom/quota/test/xpcshell/caching/groupMismatch_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..a5ccc05aa9 --- /dev/null +++ b/dom/quota/test/xpcshell/caching/removedOrigin_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..2b14ca7276 --- /dev/null +++ b/dom/quota/test/xpcshell/caching/unsetLastAccessTime_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..7d7985ddd0 --- /dev/null +++ b/dom/quota/test/xpcshell/clearStoragesForPrincipal_profile.zip diff --git a/dom/quota/test/xpcshell/common/head.js b/dom/quota/test/xpcshell/common/head.js new file mode 100644 index 0000000000..3471b56de7 --- /dev/null +++ b/dom/quota/test/xpcshell/common/head.js @@ -0,0 +1,629 @@ +/** + * 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 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 Binary files differnew file mode 100644 index 0000000000..d5958dbd59 --- /dev/null +++ b/dom/quota/test/xpcshell/createLocalStorage_profile.zip diff --git a/dom/quota/test/xpcshell/defaultStorageDirectory_shared.json b/dom/quota/test/xpcshell/defaultStorageDirectory_shared.json new file mode 100644 index 0000000000..a59cebdc87 --- /dev/null +++ b/dom/quota/test/xpcshell/defaultStorageDirectory_shared.json @@ -0,0 +1,109 @@ +[ + { "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 } + ] + } + ] + } + ] + } +] diff --git a/dom/quota/test/xpcshell/defaultStorageDirectory_shared.zip b/dom/quota/test/xpcshell/defaultStorageDirectory_shared.zip Binary files differnew file mode 100644 index 0000000000..61e5b60a87 --- /dev/null +++ b/dom/quota/test/xpcshell/defaultStorageDirectory_shared.zip diff --git a/dom/quota/test/xpcshell/getUsage_profile.zip b/dom/quota/test/xpcshell/getUsage_profile.zip Binary files differnew file mode 100644 index 0000000000..5144112bde --- /dev/null +++ b/dom/quota/test/xpcshell/getUsage_profile.zip diff --git a/dom/quota/test/xpcshell/groupMismatch_profile.zip b/dom/quota/test/xpcshell/groupMismatch_profile.zip Binary files differnew file mode 100644 index 0000000000..182b013de0 --- /dev/null +++ b/dom/quota/test/xpcshell/groupMismatch_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..6b959e7525 --- /dev/null +++ b/dom/quota/test/xpcshell/indexedDBDirectory_shared.zip diff --git a/dom/quota/test/xpcshell/make_unknownFiles.js b/dom/quota/test/xpcshell/make_unknownFiles.js new file mode 100644 index 0000000000..dd8213ade4 --- /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..5e1cb50631 --- /dev/null +++ b/dom/quota/test/xpcshell/originMismatch_profile.json @@ -0,0 +1,74 @@ +[ + { "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 Binary files differnew file mode 100644 index 0000000000..dd5795736f --- /dev/null +++ b/dom/quota/test/xpcshell/originMismatch_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..b80bfe904b --- /dev/null +++ b/dom/quota/test/xpcshell/persistentStorageDirectory_shared.zip diff --git a/dom/quota/test/xpcshell/removeLocalStorage1_profile.zip b/dom/quota/test/xpcshell/removeLocalStorage1_profile.zip Binary files differnew file mode 100644 index 0000000000..19e971433c --- /dev/null +++ b/dom/quota/test/xpcshell/removeLocalStorage1_profile.zip diff --git a/dom/quota/test/xpcshell/removeLocalStorage2_profile.zip b/dom/quota/test/xpcshell/removeLocalStorage2_profile.zip Binary files differnew file mode 100644 index 0000000000..04d1a3462b --- /dev/null +++ b/dom/quota/test/xpcshell/removeLocalStorage2_profile.zip 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..eafbc95754 --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/test_qm_first_initialization_attempt.js @@ -0,0 +1,867 @@ +/** + * 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 Binary files differnew file mode 100644 index 0000000000..92dcfb777e --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/version0_0_make_it_unusable.zip diff --git a/dom/quota/test/xpcshell/telemetry/version0_0_profile.zip b/dom/quota/test/xpcshell/telemetry/version0_0_profile.zip Binary files differnew file mode 100644 index 0000000000..2fb0f525c2 --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/version0_0_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..92dcfb777e --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/version1_0_make_it_unusable.zip diff --git a/dom/quota/test/xpcshell/telemetry/version1_0_profile.zip b/dom/quota/test/xpcshell/telemetry/version1_0_profile.zip Binary files differnew file mode 100644 index 0000000000..6169f439c1 --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/version1_0_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..92dcfb777e --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/version2_0_make_it_unusable.zip diff --git a/dom/quota/test/xpcshell/telemetry/version2_0_profile.zip b/dom/quota/test/xpcshell/telemetry/version2_0_profile.zip Binary files differnew file mode 100644 index 0000000000..465f53cea9 --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/version2_0_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..92dcfb777e --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/version2_1_make_it_unusable.zip diff --git a/dom/quota/test/xpcshell/telemetry/version2_1_profile.zip b/dom/quota/test/xpcshell/telemetry/version2_1_profile.zip Binary files differnew file mode 100644 index 0000000000..81463235ab --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/version2_1_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..b6b8eecabf --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/version2_2_make_it_unusable.zip diff --git a/dom/quota/test/xpcshell/telemetry/version2_2_profile.zip b/dom/quota/test/xpcshell/telemetry/version2_2_profile.zip Binary files differnew file mode 100644 index 0000000000..e572726cca --- /dev/null +++ b/dom/quota/test/xpcshell/telemetry/version2_2_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..da1de0979b --- /dev/null +++ b/dom/quota/test/xpcshell/tempMetadataCleanup_profile.zip 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_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..282894beeb --- /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..223e6a9401 --- /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..2e95b2d749 --- /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..9ddfbed56b --- /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 Binary files differnew file mode 100644 index 0000000000..96d636780d --- /dev/null +++ b/dom/quota/test/xpcshell/unknownFiles_profile.zip diff --git a/dom/quota/test/xpcshell/unsetLastAccessTime_profile.zip b/dom/quota/test/xpcshell/unsetLastAccessTime_profile.zip Binary files differnew file mode 100644 index 0000000000..2b14ca7276 --- /dev/null +++ b/dom/quota/test/xpcshell/unsetLastAccessTime_profile.zip 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..eff5918e98 --- /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 Binary files differnew file mode 100644 index 0000000000..c09b503c18 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/cacheVersion1_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..63936ecf9a --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/indexedDBAndPersistentStorageDirectory_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..a0a56a77df --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_flatOriginDirectories_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..589e65ec82 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/indexedDBDirectory_profile.zip diff --git a/dom/quota/test/xpcshell/upgrades/localStorageArchive1upgrade_profile.zip b/dom/quota/test/xpcshell/upgrades/localStorageArchive1upgrade_profile.zip Binary files differnew file mode 100644 index 0000000000..a17b90dedf --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/localStorageArchive1upgrade_profile.zip diff --git a/dom/quota/test/xpcshell/upgrades/localStorageArchive4upgrade_profile.zip b/dom/quota/test/xpcshell/upgrades/localStorageArchive4upgrade_profile.zip Binary files differnew file mode 100644 index 0000000000..cf5b29adae --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/localStorageArchive4upgrade_profile.zip diff --git a/dom/quota/test/xpcshell/upgrades/localStorageArchiveDowngrade_profile.zip b/dom/quota/test/xpcshell/upgrades/localStorageArchiveDowngrade_profile.zip Binary files differnew file mode 100644 index 0000000000..2f11abe858 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/localStorageArchiveDowngrade_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..9ddd9af6a9 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/persistentAndDefaultStorageDirectory_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..436ebf9070 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_flatOriginDirectories_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..0c5200adf4 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_originDirectories_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..ab8c045be9 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/persistentStorageDirectory_profile.zip 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..e9f1ae2291 --- /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..17946ffff5 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/test_upgradeStorageFrom1_0_removeAppsData.js @@ -0,0 +1,101 @@ +/** + * 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 Binary files differnew file mode 100644 index 0000000000..5ef577191c --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/version0_0_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..582edb43af --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/version1_0_appsData_profile.zip 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..a8fb8b2260 --- /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 Binary files differnew file mode 100644 index 0000000000..8abfae79c2 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/version1_0_idb_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..88543784ec --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/version1_0_morgueDirectory_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..2b4125edf9 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/version1_0_obsoleteOriginAttributes_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..c140df56e4 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/version2_0_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..908dac7058 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/version2_1_profile.zip 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 Binary files differnew file mode 100644 index 0000000000..b6ae7e7d76 --- /dev/null +++ b/dom/quota/test/xpcshell/upgrades/version2_2_profile.zip 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..2b85faed16 --- /dev/null +++ b/dom/quota/test/xpcshell/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 +tags = condprof +support-files = + basics_profile.zip + clearStoragesForPrincipal_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_clearStoragesForOriginAttributesPattern.js] +[test_estimateOrigin.js] +[test_getUsage.js] +[test_groupMismatch.js] +[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] |