summaryrefslogtreecommitdiffstats
path: root/dom/cache/test/xpcshell
diff options
context:
space:
mode:
Diffstat (limited to 'dom/cache/test/xpcshell')
-rw-r--r--dom/cache/test/xpcshell/bug1425146_profile.zipbin0 -> 2834 bytes
-rw-r--r--dom/cache/test/xpcshell/head.js208
-rw-r--r--dom/cache/test/xpcshell/make_profile.js137
-rw-r--r--dom/cache/test/xpcshell/schema_15_profile.zipbin0 -> 3111 bytes
-rw-r--r--dom/cache/test/xpcshell/schema_25_profile.zipbin0 -> 60710 bytes
-rw-r--r--dom/cache/test/xpcshell/test_bug1425146.js51
-rw-r--r--dom/cache/test/xpcshell/test_empty_directories.js85
-rw-r--r--dom/cache/test/xpcshell/test_migration.js65
-rw-r--r--dom/cache/test/xpcshell/test_originInit.js249
-rw-r--r--dom/cache/test/xpcshell/test_padding_error_handle.js72
-rw-r--r--dom/cache/test/xpcshell/test_schema_26_upgrade.js18
-rw-r--r--dom/cache/test/xpcshell/xpcshell.ini21
12 files changed, 906 insertions, 0 deletions
diff --git a/dom/cache/test/xpcshell/bug1425146_profile.zip b/dom/cache/test/xpcshell/bug1425146_profile.zip
new file mode 100644
index 0000000000..fc57bc1043
--- /dev/null
+++ b/dom/cache/test/xpcshell/bug1425146_profile.zip
Binary files differ
diff --git a/dom/cache/test/xpcshell/head.js b/dom/cache/test/xpcshell/head.js
new file mode 100644
index 0000000000..58717f0f68
--- /dev/null
+++ b/dom/cache/test/xpcshell/head.js
@@ -0,0 +1,208 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/
+ * and are CC licensed by https://www.flickr.com/photos/legofenris/.
+ */
+
+// testSteps is expected to be defined by the file including this file.
+/* global testSteps */
+
+const NS_APP_USER_PROFILE_50_DIR = "ProfD";
+const osWindowsName = "WINNT";
+const pathDelimiter = "/";
+
+const persistentPersistence = "persistent";
+const defaultPersistence = "default";
+
+const storageDirName = "storage";
+const persistentPersistenceDirName = "permanent";
+const defaultPersistenceDirName = "default";
+
+function cacheClientDirName() {
+ return "cache";
+}
+
+// services required be initialized in order to run CacheStorage
+var ss = Cc["@mozilla.org/storage/service;1"].createInstance(
+ Ci.mozIStorageService
+);
+var sts = Cc["@mozilla.org/network/stream-transport-service;1"].getService(
+ Ci.nsIStreamTransportService
+);
+var hash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+
+class RequestError extends Error {
+ constructor(resultCode, resultName) {
+ super(`Request failed (code: ${resultCode}, name: ${resultName})`);
+ this.name = "RequestError";
+ this.resultCode = resultCode;
+ this.resultName = resultName;
+ }
+}
+
+add_setup(function () {
+ do_get_profile();
+
+ enableTesting();
+
+ // Expose Cache and Fetch symbols on the global
+ Cu.importGlobalProperties(["caches", "fetch"]);
+
+ registerCleanupFunction(resetTesting);
+});
+
+function enableTesting() {
+ Services.prefs.setBoolPref("dom.simpleDB.enabled", true);
+ Services.prefs.setBoolPref("dom.quotaManager.testing", true);
+}
+
+function resetTesting() {
+ Services.prefs.clearUserPref("dom.quotaManager.testing");
+ Services.prefs.clearUserPref("dom.simpleDB.enabled");
+}
+
+function initStorage() {
+ return Services.qms.init();
+}
+
+function initTemporaryStorage() {
+ return Services.qms.initTemporaryStorage();
+}
+
+function initPersistentOrigin(principal) {
+ return Services.qms.initializePersistentOrigin(principal);
+}
+
+function initTemporaryOrigin(principal) {
+ return Services.qms.initializeTemporaryOrigin("default", principal);
+}
+
+function clearOrigin(principal, persistence) {
+ let request = Services.qms.clearStoragesForPrincipal(principal, persistence);
+
+ return request;
+}
+
+function reset() {
+ return Services.qms.reset();
+}
+
+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;
+}
+
+// Extract a zip file into the profile
+function create_test_profile(zipFileName) {
+ var directoryService = Services.dirsvc;
+
+ var profileDir = directoryService.get(NS_APP_USER_PROFILE_50_DIR, Ci.nsIFile);
+ var currentDir = directoryService.get("CurWorkD", Ci.nsIFile);
+
+ var packageFile = currentDir.clone();
+ packageFile.append(zipFileName);
+
+ var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(
+ Ci.nsIZipReader
+ );
+ zipReader.open(packageFile);
+
+ var entryNames = Array.from(zipReader.findEntries(null));
+ entryNames.sort();
+
+ for (var entryName of entryNames) {
+ var zipentry = zipReader.getEntry(entryName);
+
+ var file = profileDir.clone();
+ entryName.split(pathDelimiter).forEach(function (part) {
+ file.append(part);
+ });
+
+ if (zipentry.isDirectory) {
+ file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
+ } else {
+ var 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);
+
+ var bostream = Cc[
+ "@mozilla.org/network/buffered-output-stream;1"
+ ].createInstance(Ci.nsIBufferedOutputStream);
+ bostream.init(ostream, 32 * 1024);
+
+ bostream.writeFrom(istream, istream.available());
+
+ istream.close();
+ bostream.close();
+ }
+ }
+
+ zipReader.close();
+}
+
+function getCacheDir() {
+ return getRelativeFile(
+ `${storageDirName}/${defaultPersistenceDirName}/chrome/${cacheClientDirName()}`
+ );
+}
+
+function getPrincipal(url, attrs) {
+ let uri = Services.io.newURI(url);
+ if (!attrs) {
+ attrs = {};
+ }
+ return Services.scriptSecurityManager.createContentPrincipal(uri, attrs);
+}
+
+function getDefaultPrincipal() {
+ return getPrincipal("http://example.com");
+}
+
+function getRelativeFile(relativePath) {
+ let file = Services.dirsvc
+ .get(NS_APP_USER_PROFILE_50_DIR, Ci.nsIFile)
+ .clone();
+
+ if (Services.appinfo.OS === osWindowsName) {
+ let winFile = file.QueryInterface(Ci.nsILocalFileWin);
+ winFile.useDOSDevicePathSyntax = true;
+ }
+
+ relativePath.split(pathDelimiter).forEach(function (component) {
+ if (component == "..") {
+ file = file.parent;
+ } else {
+ file.append(component);
+ }
+ });
+
+ return file;
+}
+
+function getSimpleDatabase(principal, persistence) {
+ let connection = Cc["@mozilla.org/dom/sdb-connection;1"].createInstance(
+ Ci.nsISDBConnection
+ );
+
+ if (!principal) {
+ principal = getDefaultPrincipal();
+ }
+
+ connection.init(principal, persistence);
+
+ return connection;
+}
diff --git a/dom/cache/test/xpcshell/make_profile.js b/dom/cache/test/xpcshell/make_profile.js
new file mode 100644
index 0000000000..b7b1a9042b
--- /dev/null
+++ b/dom/cache/test/xpcshell/make_profile.js
@@ -0,0 +1,137 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/
+ * and are CC licensed by https://www.flickr.com/photos/legofenris/.
+ */
+
+// Enumerate the directory tree and store results in entryList as
+//
+// { path: 'a/b/c', file: <nsIFile> }
+//
+// The algorithm starts with the first entry already in entryList.
+function enumerate_tree(entryList) {
+ for (var index = 0; index < entryList.length; ++index) {
+ var path = entryList[index].path;
+ var file = entryList[index].file;
+
+ if (file.isDirectory()) {
+ var dirList = file.directoryEntries;
+ while (dirList.hasMoreElements()) {
+ var dirFile = dirList.nextFile;
+ entryList.push({ path: path + "/" + dirFile.leafName, file: dirFile });
+ }
+ }
+ }
+}
+
+function zip_profile(zipFile, profileDir) {
+ var zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance(
+ Ci.nsIZipWriter
+ );
+ zipWriter.open(zipFile, 0x04 | 0x08 | 0x20);
+
+ var root = profileDir.clone();
+ root.append("storage");
+ root.append("default");
+ root.append("chrome");
+
+ var entryList = [{ path: "storage/default/chrome", file: root }];
+ enumerate_tree(entryList);
+
+ entryList.forEach(function (entry) {
+ if (entry.file.isDirectory()) {
+ zipWriter.addEntryDirectory(
+ entry.path,
+ entry.file.lastModifiedTime,
+ false
+ );
+ } else {
+ var istream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ istream.init(entry.file, -1, -1, 0);
+ zipWriter.addEntryStream(
+ entry.path,
+ entry.file.lastModifiedTime,
+ Ci.nsIZipWriter.COMPRESSION_DEFAULT,
+ istream,
+ false
+ );
+ istream.close();
+ }
+ });
+
+ zipWriter.close();
+}
+
+function exactGC() {
+ return new Promise(function (resolve) {
+ var count = 0;
+ function doPreciseGCandCC() {
+ function scheduleGCCallback() {
+ Cu.forceCC();
+
+ if (++count < 2) {
+ doPreciseGCandCC();
+ } else {
+ resolve();
+ }
+ }
+ Cu.schedulePreciseGC(scheduleGCCallback);
+ }
+ doPreciseGCandCC();
+ });
+}
+
+function resetQuotaManager() {
+ return new Promise(function (resolve) {
+ var prefService = Services.prefs;
+
+ // enable quota manager testing mode
+ var pref = "dom.quotaManager.testing";
+ prefService.getBranch(null).setBoolPref(pref, true);
+
+ var request = Services.qms.reset();
+ request.callback = resolve;
+
+ // disable quota manager testing mode
+ // prefService.getBranch(null).setBoolPref(pref, false);
+ });
+}
+
+function run_test() {
+ do_test_pending();
+ do_get_profile();
+
+ var directoryService = Services.dirsvc;
+ var profileDir = directoryService.get("ProfD", Ci.nsIFile);
+ var currentDir = directoryService.get("CurWorkD", Ci.nsIFile);
+
+ var zipFile = currentDir.clone();
+ zipFile.append("new_profile.zip");
+ if (zipFile.exists()) {
+ zipFile.remove(false);
+ }
+ ok(!zipFile.exists());
+
+ caches
+ .open("xpcshell-test")
+ .then(function (c) {
+ var request = new Request("http://example.com/index.html");
+ var response = new Response("hello world");
+ return c.put(request, response);
+ })
+ .then(exactGC)
+ .then(resetQuotaManager)
+ .then(function () {
+ zip_profile(zipFile, profileDir);
+ dump("### ### created zip at: " + zipFile.path + "\n");
+ do_test_finished();
+ })
+ .catch(function (e) {
+ do_test_finished();
+ ok(false, e);
+ });
+}
diff --git a/dom/cache/test/xpcshell/schema_15_profile.zip b/dom/cache/test/xpcshell/schema_15_profile.zip
new file mode 100644
index 0000000000..32cc8f2eeb
--- /dev/null
+++ b/dom/cache/test/xpcshell/schema_15_profile.zip
Binary files differ
diff --git a/dom/cache/test/xpcshell/schema_25_profile.zip b/dom/cache/test/xpcshell/schema_25_profile.zip
new file mode 100644
index 0000000000..626aa8f625
--- /dev/null
+++ b/dom/cache/test/xpcshell/schema_25_profile.zip
Binary files differ
diff --git a/dom/cache/test/xpcshell/test_bug1425146.js b/dom/cache/test/xpcshell/test_bug1425146.js
new file mode 100644
index 0000000000..d678bb3745
--- /dev/null
+++ b/dom/cache/test/xpcshell/test_bug1425146.js
@@ -0,0 +1,51 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This test is mainly to verify that we are able to recover from a situation
+// in which the padding file and the padding column are missing during origin
+// initialization. This was originally reported in bug 1425146 comment 39.
+// The situation can be described as follows:
+// 1. A profile is used in FF57. The storage version is 2.1. There's no cache
+// storage for http://www.mozilla.org
+// 2. The same profile is used in FF56. The storage version is still 2.1 which
+// doesn't prevent storage system from working since minor upgrades are
+// backwards-compatible. The cache storage for http://www.mozilla.org is
+// created with schema version 25 (without any padding stuff).
+// 3. The profile is used in FF57 again. Zero padding files for existing cache
+// storages are not created because storage is already at version 2.1.
+// Storage is being initialized and a missing padding file triggers padding
+// size computation from the cache database with schema version 25. Since
+// the computation happens before any real DOM cache operation, the database
+// is not upgraded to schema version 26, so the padding column is missing.
+
+add_task(async function testSteps() {
+ // The profile contains one cache storage, a script for cache creation and
+ // the storage database:
+ // - storage/default/http+++www.mozilla.org/cache
+ // - create_cache.js
+ // - storage.sqlite
+ // 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/cache/test/xpcshell/create_cache.js
+ // Note: it must be executed in FF56 and it only creates the directory
+ // "storage/default/chrome/cache" and the file "storage.sqlite". To make it
+ // become the profile in the test, additional manual steps are needed.
+ // 1. Create "http+++www.mozilla.org" folder under the ""storage/default".
+ // 2. Copy the "cache" folder under the "storage/default/chrome" to
+ // "storage/default/http+++www.mozilla.org".
+ // 3. Remove the folder "storage/default/chrome"
+ // 4. Remove the folder "storage/temporary".
+ // 5. Add "create_cache.js".
+ // 6. Replace the "storage.sqlite" created by FF56 (storage v2.0) with the
+ // "storage.sqlite" created by FF57 (storage v2.1)
+ create_test_profile("bug1425146_profile.zip");
+
+ try {
+ await caches.open("test");
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+});
diff --git a/dom/cache/test/xpcshell/test_empty_directories.js b/dom/cache/test/xpcshell/test_empty_directories.js
new file mode 100644
index 0000000000..2e43ba67bd
--- /dev/null
+++ b/dom/cache/test/xpcshell/test_empty_directories.js
@@ -0,0 +1,85 @@
+/**
+ * This test is mainly to verify cache won't leave emptry directory.
+ */
+
+function resetStorage() {
+ return new Promise(function (resolve, reject) {
+ var qms = Services.qms;
+ var request = qms.reset();
+ request.callback = resolve;
+ });
+}
+
+async function setUpEnv() {
+ Services.prefs.setBoolPref("dom.quotaManager.testing", true);
+
+ // We need this for generating the basic profile path
+ create_test_profile("schema_25_profile.zip");
+
+ // Trigger storage upgrade
+ await caches.open("test");
+ const cacheDir = getCacheDir();
+ let morgueDir = cacheDir.clone();
+ morgueDir.append("morgue");
+
+ // clean the cache directoy
+ for (let dir of morgueDir.directoryEntries) {
+ for (let file of dir.directoryEntries) {
+ file.remove(false);
+ }
+ }
+
+ await resetStorage();
+}
+
+// This function ensure the directory with file shouldn't have been deleted and
+// ensure the directory without file should've been deleted.
+function verifyResult() {
+ const cacheDir = getCacheDir();
+ let morgueDir = cacheDir.clone();
+ morgueDir.append("morgue");
+
+ let foundEmpty = false;
+ for (let dir of morgueDir.directoryEntries) {
+ let empty = true;
+ // eslint-disable-next-line no-unused-vars
+ for (let file of dir.directoryEntries) {
+ empty = false;
+ }
+
+ foundEmpty = foundEmpty || empty;
+ }
+ return !foundEmpty;
+}
+
+add_task(async function testSteps() {
+ const url = "https://www.mozilla.org";
+
+ info("Setting up environment");
+
+ await setUpEnv();
+
+ info("Test 0 - InitOrigin shouldn't leave an empty directoy");
+
+ let cache = await caches.open("test");
+ let response = await cache.match(url);
+ ok(!!response, "Upgrade from 25 to 26 do succeed");
+ ok(verifyResult(), "InitOrigin should clean all empty directories");
+
+ info("Test 1 - DeleteOrphanedBodyFiles shouldn't leave an empty directoy");
+
+ await cache.put(url, response.clone());
+ // eslint-disable-next-line no-unused-vars
+ let r = await cache.match(url);
+ await cache.delete(url);
+ await resetStorage();
+
+ cache = await caches.open("test");
+
+ // Extra operation to ensure the deletion is completed
+ await cache.match(url);
+
+ ok(verifyResult(), "Empty directory should be removed");
+
+ await caches.delete("test");
+});
diff --git a/dom/cache/test/xpcshell/test_migration.js b/dom/cache/test/xpcshell/test_migration.js
new file mode 100644
index 0000000000..5a6637eecc
--- /dev/null
+++ b/dom/cache/test/xpcshell/test_migration.js
@@ -0,0 +1,65 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/
+ * and are CC licensed by https://www.flickr.com/photos/legofenris/.
+ */
+
+add_task(async function testSteps() {
+ create_test_profile("schema_15_profile.zip");
+
+ const cache = await caches.open("xpcshell-test");
+ ok(cache, "cache exists");
+
+ const requestList = await cache.keys();
+
+ ok(requestList.length, "should have at least one request in cache");
+ for (const request of requestList) {
+ ok(request, "each request in list should be non-null");
+ ok(
+ request.redirect === "follow",
+ 'request.redirect should default to "follow"'
+ );
+ ok(
+ request.cache === "default",
+ 'request.cache should have been updated to "default"' + request.cache
+ );
+ ok(
+ request.mode === "navigate",
+ 'request.mode should have been updated to "navigate"'
+ );
+ ok(
+ request.referrerPolicy === "no-referrer-when-downgrade",
+ 'request.referrerPolicy should have been updated to "no-referrer-when-downgrade"'
+ );
+ }
+
+ const responseList = await Promise.all(
+ requestList.map(function (request) {
+ return cache.match(request);
+ })
+ );
+
+ ok(responseList.length, "should have at least one response in cache");
+ for (const response of responseList) {
+ ok(response, "each response should be non-null");
+ // reponse.url is a empty string in current test file. It should test for
+ // not being a empty string once thet test file is updated.
+ ok(
+ typeof response.url === "string",
+ "each response.url should be a string"
+ );
+ // reponse.redirected may be changed once test file is updated. It should
+ // be false since current reponse.url is a empty string.
+ ok(
+ response.redirected === false,
+ "each response.redirected should be false"
+ );
+ Assert.equal(
+ response.headers.get("Content-Type"),
+ "text/plain;charset=UTF-8",
+ "the response should have the correct header"
+ );
+ }
+});
diff --git a/dom/cache/test/xpcshell/test_originInit.js b/dom/cache/test/xpcshell/test_originInit.js
new file mode 100644
index 0000000000..395cba8a0f
--- /dev/null
+++ b/dom/cache/test/xpcshell/test_originInit.js
@@ -0,0 +1,249 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(async function testSteps() {
+ // ToDo: Replace storage and default with a getter function once we expose the
+ // filenames of them to a IDL file.
+ const basePath = `${storageDirName}/${defaultPersistenceDirName}/`;
+ const principal = getPrincipal("https://example.com");
+ const originDirName = "https+++example.com";
+
+ // ToDo: Replace caches.sqlite with a getter function once we expose the
+ // filename of it to a IDL file.
+ const cachesDatabase = getRelativeFile(
+ `${basePath}/${originDirName}/${cacheClientDirName()}/caches.sqlite`
+ );
+ // ToDo: Replace .padding with a getter function once we expose the filename
+ // of it to a IDL file.
+ const paddingFile = getRelativeFile(
+ `${basePath}/${originDirName}/${cacheClientDirName()}/.padding`
+ );
+ // ToDo: Replace .padding-tmp with a getter function once we expose the
+ // filename of it to a IDL file.
+ const paddingTempFile = getRelativeFile(
+ `${basePath}/${originDirName}/${cacheClientDirName()}/.padding-tmp`
+ );
+ // ToDo: Replace morgue with a getter function once we expose the
+ // filename of it to a IDL file.
+ const morgueDir = getRelativeFile(
+ `${basePath}/${originDirName}/${cacheClientDirName()}/morgue`
+ );
+
+ const persistentCacheDir = getRelativeFile(
+ `${storageDirName}/${persistentPersistenceDirName}/${originDirName}/` +
+ `${cacheClientDirName()}`
+ );
+
+ async function createNormalCacheOrigin() {
+ async function sandboxScript() {
+ const cache = await caches.open("myCache");
+ const request = new Request("https://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;
+
+ let request = reset();
+ await requestFinished(request);
+ }
+
+ async function createPersistentTestOrigin() {
+ let database = getSimpleDatabase(principal, "persistent");
+
+ let request = database.open("data");
+ await requestFinished(request);
+
+ request = reset();
+ await requestFinished(request);
+ }
+
+ function removeFile(file) {
+ file.remove(false);
+ }
+
+ function removeDir(dir) {
+ dir.remove(true);
+ }
+
+ function createEmptyFile(file) {
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ }
+
+ function createEmptyDirectory(dir) {
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o0755);
+ }
+
+ async function initTestOrigin() {
+ let request = initStorage();
+ await requestFinished(request);
+
+ request = initTemporaryStorage();
+ await requestFinished(request);
+
+ request = initTemporaryOrigin(principal);
+ await requestFinished(request);
+ }
+
+ async function initPersistentTestOrigin() {
+ let request = initStorage();
+ await requestFinished(request);
+
+ request = initPersistentOrigin(principal);
+ await requestFinished(request);
+ }
+
+ function checkFiles(
+ expectCachesDatabase,
+ expectPaddingFile,
+ expectTempPaddingFile,
+ expectMorgueDir
+ ) {
+ let exists = cachesDatabase.exists();
+ if (expectCachesDatabase) {
+ ok(exists, "caches.sqlite does exist");
+ } else {
+ ok(!exists, "caches.sqlite doesn't exist");
+ }
+
+ exists = paddingFile.exists();
+ if (expectPaddingFile) {
+ ok(exists, ".padding does exist");
+ } else {
+ ok(!exists, ".padding doesn't exist");
+ }
+
+ exists = paddingTempFile.exists();
+ if (expectTempPaddingFile) {
+ ok(exists, ".padding-tmp does exist");
+ } else {
+ ok(!exists, ".padding-tmp doesn't exist");
+ }
+
+ exists = morgueDir.exists();
+ if (expectMorgueDir) {
+ ok(exists, "morgue does exist");
+ } else {
+ ok(!exists, "moruge doesn't exist");
+ }
+ }
+
+ async function clearTestOrigin() {
+ let request = clearOrigin(principal, defaultPersistence);
+ await requestFinished(request);
+ }
+
+ async function clearPersistentTestOrigin() {
+ let request = clearOrigin(principal, persistentPersistence);
+ await requestFinished(request);
+ }
+
+ async function testOriginInit(
+ createCachesDatabase,
+ createPaddingFile,
+ createTempPaddingFile,
+ createMorgueDir
+ ) {
+ info(
+ `Testing initialization of cache directory when caches.sqlite ` +
+ `${createCachesDatabase ? "exists" : "doesn't exist"}, .padding ` +
+ `${createPaddingFile ? "exists" : "doesn't exist"}, .padding-tmp ` +
+ `${createTempPaddingFile ? "exists" : "doesn't exist"}, morgue ` +
+ `${createMorgueDir ? "exists" : "doesn't exist"}`
+ );
+
+ await createNormalCacheOrigin();
+
+ checkFiles(true, true, false, true);
+
+ if (!createCachesDatabase) {
+ removeFile(cachesDatabase);
+ }
+
+ if (!createPaddingFile) {
+ removeFile(paddingFile);
+ }
+
+ if (createTempPaddingFile) {
+ createEmptyFile(paddingTempFile);
+ }
+
+ if (!createMorgueDir) {
+ removeDir(morgueDir);
+ }
+
+ await initTestOrigin();
+
+ checkFiles(
+ createCachesDatabase,
+ // After the origin is initialized, ".padding" should only exist when
+ // "caches.sqlite" exists.
+ createCachesDatabase,
+ // After the origin is initialized, ".padding-tmp" should have always been
+ // removed.
+ false,
+ createCachesDatabase && createMorgueDir
+ );
+
+ await clearTestOrigin();
+ }
+
+ // Test all possible combinations.
+ for (let createCachesDatabase of [false, true]) {
+ for (let createPaddingFile of [false, true]) {
+ for (let createTempPaddingFile of [false, true]) {
+ for (let createMorgueDir of [false, true]) {
+ await testOriginInit(
+ createCachesDatabase,
+ createPaddingFile,
+ createTempPaddingFile,
+ createMorgueDir
+ );
+ }
+ }
+ }
+ }
+
+ // Verify that InitializeOrigin doesn't fail when a
+ // storage/permanent/${origin}/cache exists.
+ async function testPermanentCacheDir() {
+ info(
+ "Testing initialization of cache directory placed in permanent origin " +
+ "directory"
+ );
+
+ await createPersistentTestOrigin();
+
+ createEmptyDirectory(persistentCacheDir);
+
+ try {
+ await initPersistentTestOrigin();
+
+ ok(true, "Should not have thrown");
+ } catch (ex) {
+ ok(false, "Should not have thrown");
+ }
+
+ let exists = persistentCacheDir.exists();
+ ok(exists, "cache directory in permanent origin directory does exist");
+
+ await clearPersistentTestOrigin();
+ }
+
+ await testPermanentCacheDir();
+});
diff --git a/dom/cache/test/xpcshell/test_padding_error_handle.js b/dom/cache/test/xpcshell/test_padding_error_handle.js
new file mode 100644
index 0000000000..1bb1e1804f
--- /dev/null
+++ b/dom/cache/test/xpcshell/test_padding_error_handle.js
@@ -0,0 +1,72 @@
+/**
+ * This test is mainly to verify cache actions work as usual even there exists
+ * an unexpected padding file.
+ */
+
+function getTempPaddingFilePath() {
+ let cacheDir = getCacheDir();
+ let temporaryPaddingFile = cacheDir.clone();
+ temporaryPaddingFile.append(".padding-tmp");
+ return temporaryPaddingFile;
+}
+
+function createTempPaddingFile() {
+ let temporaryPaddingFile = getTempPaddingFilePath();
+ temporaryPaddingFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+
+ ok(
+ temporaryPaddingFile.exists(),
+ "Temporary padding file does be created by test"
+ );
+}
+
+add_task(async function testSteps() {
+ create_test_profile("schema_25_profile.zip");
+ let cache = await caches.open("test");
+
+ // Step 1: Verify cache.match won't fail when there is a temporary padding
+ // file
+ createTempPaddingFile();
+
+ let response = await cache.match("https://www.mozilla.org");
+ ok(!!response, "Upgrade from 25 to 26 do succeed");
+
+ // Note: Only cache write actions(e.g. cache.put/add/addAll/delete) will
+ // remove unexpected temporary padding file when writting an opaque response
+ // into the file-system. Cache read actions(e.g. cache.keys/match) won't.
+ let temporaryPaddingFile = getTempPaddingFilePath();
+ ok(
+ temporaryPaddingFile.exists(),
+ "Temporary padding file doesn't be removed by cache.match"
+ );
+
+ // Step 2: Verify cache.put won't fail when there is a temporary padding
+ // file
+ await cache.put("https://foo.com", response);
+ ok(
+ !temporaryPaddingFile.exists(),
+ "Temporary padding file does be removed by cache.put"
+ );
+
+ // Step 3: Verify cache.keys won't fail when there is a temporary padding
+ // file
+ createTempPaddingFile();
+
+ let cacheEntries = await cache.keys("https://foo.com");
+ ok(cacheEntries.length === 1, "Cache.put does succeed");
+
+ ok(
+ temporaryPaddingFile.exists(),
+ "Temporary padding file doesn't be removed by cache.keys"
+ );
+
+ // Step 4: Verify cache.delete won't fail when there is a temporary padding
+ // file
+ await cache.delete("https://foo.com");
+ ok(
+ !temporaryPaddingFile.exists(),
+ "Temporary padding file does be removed by cache.delete"
+ );
+
+ await caches.delete("test");
+});
diff --git a/dom/cache/test/xpcshell/test_schema_26_upgrade.js b/dom/cache/test/xpcshell/test_schema_26_upgrade.js
new file mode 100644
index 0000000000..3bdb789e17
--- /dev/null
+++ b/dom/cache/test/xpcshell/test_schema_26_upgrade.js
@@ -0,0 +1,18 @@
+/**
+ * The schema_25_profile.zip are made from local Nightly by following step
+ * 1. Go to any website
+ * 2. Open web console and type
+ * caches.open("test")
+ * .then(c => fetch("https://www.mozilla.org", {mode:"no-cors"})
+ * .then(r => c.put("https://www.mozilla.org", r)));
+ * 3. Go to profile directory and rename the website folder to "chrome"
+ */
+
+add_task(async function testSteps() {
+ create_test_profile("schema_25_profile.zip");
+
+ let cache = await caches.open("test");
+ let response = await cache.match("https://www.mozilla.org");
+ ok(!!response, "Upgrade from 25 to 26 do succeed");
+ ok(response.type === "opaque", "The response type does be opaque");
+});
diff --git a/dom/cache/test/xpcshell/xpcshell.ini b/dom/cache/test/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..b1640ce42d
--- /dev/null
+++ b/dom/cache/test/xpcshell/xpcshell.ini
@@ -0,0 +1,21 @@
+# 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 =
+ bug1425146_profile.zip
+ schema_15_profile.zip
+ schema_25_profile.zip
+
+# dummy test entry to generate profile zip files
+[make_profile.js]
+ skip-if = true
+
+[test_bug1425146.js]
+[test_empty_directories.js]
+[test_migration.js]
+[test_originInit.js]
+[test_padding_error_handle.js]
+[test_schema_26_upgrade.js]