summaryrefslogtreecommitdiffstats
path: root/browser/components/downloads/test/unit
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/downloads/test/unit')
-rw-r--r--browser/components/downloads/test/unit/head.js67
-rw-r--r--browser/components/downloads/test/unit/test_DownloadLastDir_basics.js140
-rw-r--r--browser/components/downloads/test/unit/test_DownloadsCommon_getMimeInfo.js168
-rw-r--r--browser/components/downloads/test/unit/test_DownloadsCommon_isFileOfType.js147
-rw-r--r--browser/components/downloads/test/unit/test_DownloadsViewableInternally.js277
-rw-r--r--browser/components/downloads/test/unit/xpcshell.ini9
6 files changed, 808 insertions, 0 deletions
diff --git a/browser/components/downloads/test/unit/head.js b/browser/components/downloads/test/unit/head.js
new file mode 100644
index 0000000000..2f0326e779
--- /dev/null
+++ b/browser/components/downloads/test/unit/head.js
@@ -0,0 +1,67 @@
+ChromeUtils.defineESModuleGetters(this, {
+ Downloads: "resource://gre/modules/Downloads.sys.mjs",
+ DownloadsCommon: "resource:///modules/DownloadsCommon.sys.mjs",
+ FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs",
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+ TestUtils: "resource://testing-common/TestUtils.sys.mjs",
+});
+ChromeUtils.defineModuleGetter(
+ this,
+ "NetUtil",
+ "resource://gre/modules/NetUtil.jsm"
+);
+
+async function createDownloadedFile(pathname, contents) {
+ info("createDownloadedFile: " + pathname);
+ let file = new FileUtils.File(pathname);
+ if (file.exists()) {
+ info(`File at ${pathname} already exists`);
+ if (!contents) {
+ ok(
+ false,
+ `A file already exists at ${pathname}, but createDownloadedFile was asked to create a non-existant file`
+ );
+ }
+ }
+ if (contents) {
+ await IOUtils.writeUTF8(pathname, contents);
+ ok(file.exists(), `Created ${pathname}`);
+ }
+ // No post-test cleanup necessary; tmp downloads directory is already removed after each test
+ return file;
+}
+
+let gDownloadDir;
+
+async function setDownloadDir() {
+ let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile).path;
+ tmpDir = PathUtils.join(
+ tmpDir,
+ "testsavedir" + Math.floor(Math.random() * 2 ** 32)
+ );
+ // Create this dir if it doesn't exist (ignores existing dirs)
+ await IOUtils.makeDirectory(tmpDir);
+ registerCleanupFunction(async function () {
+ try {
+ await IOUtils.remove(tmpDir, { recursive: true });
+ } catch (e) {
+ console.error(e);
+ }
+ });
+ Services.prefs.setIntPref("browser.download.folderList", 2);
+ Services.prefs.setCharPref("browser.download.dir", tmpDir);
+ return tmpDir;
+}
+
+/**
+ * All the tests are implemented with add_task, this starts them automatically.
+ */
+function run_test() {
+ do_get_profile();
+ run_next_test();
+}
+
+add_setup(async function test_common_initialize() {
+ gDownloadDir = await setDownloadDir();
+ Services.prefs.setCharPref("browser.download.loglevel", "Debug");
+});
diff --git a/browser/components/downloads/test/unit/test_DownloadLastDir_basics.js b/browser/components/downloads/test/unit/test_DownloadLastDir_basics.js
new file mode 100644
index 0000000000..f1dfbe4733
--- /dev/null
+++ b/browser/components/downloads/test/unit/test_DownloadLastDir_basics.js
@@ -0,0 +1,140 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Basic test for setting and retrieving a download last dir.
+// More complex tests can be found in browser/components/privatebrowsing/.
+
+const SAVE_PER_SITE_PREF_BRANCH = "browser.download.lastDir";
+const SAVE_PER_SITE_PREF = SAVE_PER_SITE_PREF_BRANCH + ".savePerSite";
+
+let { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+let { DownloadLastDir } = ChromeUtils.importESModule(
+ "resource://gre/modules/DownloadLastDir.sys.mjs"
+);
+
+add_task(
+ {
+ pref_set: [[SAVE_PER_SITE_PREF, true]],
+ },
+ async function test() {
+ let downloadLastDir = new DownloadLastDir(null);
+
+ let unknownUri = Services.io.newURI("https://unknown.org/");
+ Assert.deepEqual(
+ await downloadLastDir.getFileAsync(unknownUri),
+ null,
+ "Untracked URI, no pref set"
+ );
+
+ let dir1 = FileUtils.getDir("TmpD", ["dir1"], true);
+ let uri1 = Services.io.newURI("https://test1.moz.org");
+ downloadLastDir.setFile(uri1, dir1);
+ let dir2 = FileUtils.getDir("TmpD", ["dir2"], true);
+ let uri2 = Services.io.newURI("https://test2.moz.org");
+ downloadLastDir.setFile(uri2, dir2);
+ let dir3 = FileUtils.getDir("TmpD", ["dir3"], true);
+ downloadLastDir.setFile(null, dir3);
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(uri1)).path,
+ dir1.path,
+ "Check common URI"
+ );
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(uri2)).path,
+ dir2.path,
+ "Check common URI"
+ );
+ Assert.equal(downloadLastDir.file.path, dir3.path, "No URI");
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(unknownUri)).path,
+ dir3.path,
+ "Untracked URI, pref set"
+ );
+
+ info("Check clearHistory removes all data");
+ let subject = {};
+ Services.obs.notifyObservers(subject, "browser:purge-session-history");
+ await subject.promise;
+ Assert.deepEqual(
+ await downloadLastDir.getFileAsync(uri1),
+ null,
+ "Check common URI after clear history returns null"
+ );
+ Assert.deepEqual(
+ await downloadLastDir.getFileAsync(uri2),
+ null,
+ "Check common URI after clear history returns null"
+ );
+ Assert.deepEqual(
+ await downloadLastDir.getFileAsync(unknownUri),
+ null,
+ "Check untracked URI after clear history returns null"
+ );
+
+ // file: URIs should all point to the same folder.
+ let fileUri1 = Services.io.newURI("file:///c:/test.txt");
+ downloadLastDir.setFile(uri1, dir3);
+ let dir4 = FileUtils.getDir("TmpD", ["dir4"], true);
+ let fileUri2 = Services.io.newURI("file:///d:/test.png");
+ downloadLastDir.setFile(uri1, dir4);
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(fileUri1)).path,
+ dir4.path,
+ "Check file URI"
+ );
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(fileUri2)).path,
+ dir4.path,
+ "Check file URI"
+ );
+ let unknownFileUri = Services.io.newURI("file:///e:/test.mkv");
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(unknownFileUri)).path,
+ dir4.path,
+ "Untracked File URI, pref set"
+ );
+
+ // data: URIs should point to a folder per mime-type.
+ // Unspecified mime-type is handled as text/plain.
+ let dataUri1 = Services.io.newURI("data:text/plain;charset=UTF-8,1234");
+ downloadLastDir.setFile(dataUri1, dir1);
+ let dataUri2 = Services.io.newURI("data:image/png;base64,1234");
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(dataUri2)).path,
+ dir1.path,
+ "Check data URI"
+ );
+ let dataUri3 = Services.io.newURI("data:image/png,5678");
+ downloadLastDir.setFile(dataUri3, dir2);
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(dataUri2)).path,
+ dir2.path,
+ "Data URI was changed, same mime-type"
+ );
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(dataUri1)).path,
+ dir1.path,
+ "Data URI was not changed, different mime-type"
+ );
+ let dataUri4 = Services.io.newURI("data:,");
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(dataUri4)).path,
+ dir1.path,
+ "Data URI defaults to text/plain"
+ );
+ downloadLastDir.setFile(null, dir4);
+ let unknownDataUri = Services.io.newURI("data:application/zip,");
+ Assert.deepEqual(
+ (await downloadLastDir.getFileAsync(unknownDataUri)).path,
+ dir4.path,
+ "Untracked data URI"
+ );
+ Assert.equal(
+ (await downloadLastDir.getFileAsync(dataUri4)).path,
+ dir1.path,
+ "Data URI didn't change"
+ );
+ }
+);
diff --git a/browser/components/downloads/test/unit/test_DownloadsCommon_getMimeInfo.js b/browser/components/downloads/test/unit/test_DownloadsCommon_getMimeInfo.js
new file mode 100644
index 0000000000..3e87fa9ec9
--- /dev/null
+++ b/browser/components/downloads/test/unit/test_DownloadsCommon_getMimeInfo.js
@@ -0,0 +1,168 @@
+const DATA_PDF = atob(
+ "JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G"
+);
+
+const DOWNLOAD_TEMPLATE = {
+ source: {
+ url: "https://example.com/download",
+ },
+ target: {
+ path: "",
+ },
+ contentType: "text/plain",
+ succeeded: DownloadsCommon.DOWNLOAD_FINISHED,
+ canceled: false,
+ error: null,
+ hasPartialData: false,
+ hasBlockedData: false,
+ startTime: new Date(Date.now() - 1000),
+};
+
+const TESTFILES = {
+ "download-test.txt": "Text file contents\n",
+ "download-test.pdf": DATA_PDF,
+ "download-test.PDF": DATA_PDF,
+ "download-test.xxunknown": "Unknown file contents\n",
+ "download-test": "No extension file contents\n",
+};
+let gPublicList;
+
+add_task(async function test_setup() {
+ let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
+ Assert.ok(profileDir, "profileDir: " + profileDir);
+ for (let [filename, contents] of Object.entries(TESTFILES)) {
+ TESTFILES[filename] = await createDownloadedFile(
+ PathUtils.join(gDownloadDir, filename),
+ contents
+ );
+ }
+ gPublicList = await Downloads.getList(Downloads.PUBLIC);
+});
+
+const TESTCASES = [
+ {
+ name: "Check returned value is null when the download did not succeed",
+ testFile: "download-test.txt",
+ contentType: "text/plain",
+ succeeded: false,
+ expected: null,
+ },
+ {
+ name: "Check correct mime-info is returned when download contentType is unambiguous",
+ testFile: "download-test.txt",
+ contentType: "text/plain",
+ expected: {
+ type: "text/plain",
+ },
+ },
+ {
+ name: "Returns correct mime-info from file extension when download contentType is missing",
+ testFile: "download-test.pdf",
+ contentType: undefined,
+ expected: {
+ type: "application/pdf",
+ },
+ },
+ {
+ name: "Returns correct mime-info from file extension case-insensitively",
+ testFile: "download-test.PDF",
+ contentType: undefined,
+ expected: {
+ type: "application/pdf",
+ },
+ },
+ {
+ name: "Returns null when contentType is missing and file extension is unknown",
+ testFile: "download-test.xxunknown",
+ contentType: undefined,
+ expected: null,
+ },
+ {
+ name: "Returns contentType when contentType is ambiguous and file extension is unknown",
+ testFile: "download-test.xxunknown",
+ contentType: "application/octet-stream",
+ expected: {
+ type: "application/octet-stream",
+ },
+ },
+ {
+ name: "Returns contentType when contentType is ambiguous and there is no file extension",
+ testFile: "download-test",
+ contentType: "application/octet-stream",
+ expected: {
+ type: "application/octet-stream",
+ },
+ },
+ {
+ name: "Returns null when there's no contentType and no file extension",
+ testFile: "download-test",
+ contentType: undefined,
+ expected: null,
+ },
+];
+
+// add tests for each of the generic mime-types we recognize,
+// to ensure they prefer the associated mime-type of the target file extension
+for (let type of [
+ "application/octet-stream",
+ "binary/octet-stream",
+ "application/unknown",
+]) {
+ TESTCASES.push({
+ name: `Returns correct mime-info from file extension when contentType is generic (${type})`,
+ testFile: "download-test.pdf",
+ contentType: type,
+ expected: {
+ type: "application/pdf",
+ },
+ });
+}
+
+for (let testData of TESTCASES) {
+ let tmp = {
+ async [testData.name]() {
+ info("testing with: " + JSON.stringify(testData));
+ await test_getMimeInfo_basic_function(testData);
+ },
+ };
+ add_task(tmp[testData.name]);
+}
+
+/**
+ * Sanity test the DownloadsCommon.getMimeInfo method with test parameters
+ */
+async function test_getMimeInfo_basic_function(testData) {
+ let downloadData = {
+ ...DOWNLOAD_TEMPLATE,
+ source: "source" in testData ? testData.source : DOWNLOAD_TEMPLATE.source,
+ succeeded:
+ "succeeded" in testData
+ ? testData.succeeded
+ : DOWNLOAD_TEMPLATE.succeeded,
+ target: TESTFILES[testData.testFile],
+ contentType: testData.contentType,
+ };
+ Assert.ok(downloadData.target instanceof Ci.nsIFile, "target is a nsIFile");
+ let download = await Downloads.createDownload(downloadData);
+ await gPublicList.add(download);
+ await download.refresh();
+
+ Assert.ok(
+ await IOUtils.exists(download.target.path),
+ "The file should actually exist."
+ );
+ let result = await DownloadsCommon.getMimeInfo(download);
+ if (testData.expected) {
+ Assert.equal(
+ result.type,
+ testData.expected.type,
+ "Got expected mimeInfo.type"
+ );
+ } else {
+ Assert.equal(
+ result,
+ null,
+ `Expected null, got object with type: ${result?.type}`
+ );
+ }
+}
diff --git a/browser/components/downloads/test/unit/test_DownloadsCommon_isFileOfType.js b/browser/components/downloads/test/unit/test_DownloadsCommon_isFileOfType.js
new file mode 100644
index 0000000000..d965ac264a
--- /dev/null
+++ b/browser/components/downloads/test/unit/test_DownloadsCommon_isFileOfType.js
@@ -0,0 +1,147 @@
+const DATA_PDF = atob(
+ "JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G"
+);
+
+const DOWNLOAD_TEMPLATE = {
+ source: {
+ url: "https://download-test.com/download",
+ },
+ target: {
+ path: "",
+ },
+ contentType: "text/plain",
+ succeeded: DownloadsCommon.DOWNLOAD_FINISHED,
+ canceled: false,
+ error: null,
+ hasPartialData: false,
+ hasBlockedData: false,
+ startTime: new Date(Date.now() - 1000),
+};
+
+const TESTFILES = {
+ "download-test.pdf": DATA_PDF,
+ "download-test.xxunknown": DATA_PDF,
+ "download-test-missing.pdf": null,
+};
+let gPublicList;
+add_task(async function test_setup() {
+ let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
+ Assert.ok(profileDir, "profileDir: " + profileDir);
+ for (let [filename, contents] of Object.entries(TESTFILES)) {
+ TESTFILES[filename] = await createDownloadedFile(
+ PathUtils.join(gDownloadDir, filename),
+ contents
+ );
+ }
+ gPublicList = await Downloads.getList(Downloads.PUBLIC);
+});
+
+const TESTCASES = [
+ {
+ name: "Null download arg",
+ typeArg: "application/pdf",
+ downloadProps: null,
+ expected: /TypeError/,
+ },
+ {
+ name: "Missing type arg",
+ typeArg: undefined,
+ downloadProps: {
+ target: "download-test.pdf",
+ },
+ expected: /TypeError/,
+ },
+ {
+ name: "Empty string type arg",
+ typeArg: "",
+ downloadProps: {
+ target: "download-test.pdf",
+ },
+ expected: false,
+ },
+ {
+ name: "download succeeded, file exists, unknown extension but contentType matches",
+ typeArg: "application/pdf",
+ downloadProps: {
+ target: "download-test.xxunknown",
+ contentType: "application/pdf",
+ },
+ expected: true,
+ },
+ {
+ name: "download succeeded, file exists, contentType is generic and file extension maps to matching mime-type",
+ typeArg: "application/pdf",
+ downloadProps: {
+ target: "download-test.pdf",
+ contentType: "application/unknown",
+ },
+ expected: true,
+ },
+ {
+ name: "download did not succeed",
+ typeArg: "application/pdf",
+ downloadProps: {
+ target: "download-test.pdf",
+ contentType: "application/pdf",
+ succeeded: false,
+ },
+ expected: false,
+ },
+ {
+ name: "file does not exist",
+ typeArg: "application/pdf",
+ downloadProps: {
+ target: "download-test-missing.pdf",
+ contentType: "application/pdf",
+ },
+ expected: false,
+ },
+ {
+ name: "contentType is missing and file extension doesnt map to a known mime-type",
+ typeArg: "application/pdf",
+ downloadProps: {
+ contentType: undefined,
+ target: "download-test.xxunknown",
+ },
+ expected: false,
+ },
+];
+
+for (let testData of TESTCASES) {
+ let tmp = {
+ async [testData.name]() {
+ info("testing with: " + JSON.stringify(testData));
+ await test_isFileOfType(testData);
+ },
+ };
+ add_task(tmp[testData.name]);
+}
+
+/**
+ * Sanity test the DownloadsCommon.isFileOfType method with test parameters
+ */
+async function test_isFileOfType({ name, typeArg, downloadProps, expected }) {
+ let download, result;
+ if (downloadProps) {
+ let downloadData = {
+ ...DOWNLOAD_TEMPLATE,
+ ...downloadProps,
+ };
+ downloadData.target = TESTFILES[downloadData.target];
+ Assert.ok(downloadData.target instanceof Ci.nsIFile, "target is a nsIFile");
+ download = await Downloads.createDownload(downloadData);
+ await gPublicList.add(download);
+ await download.refresh();
+ }
+
+ if (typeof expected == "boolean") {
+ result = await DownloadsCommon.isFileOfType(download, typeArg);
+ Assert.equal(result, expected, "Expected result from call to isFileOfType");
+ } else {
+ Assert.throws(
+ () => DownloadsCommon.isFileOfType(download, typeArg),
+ expected,
+ "isFileOfType should throw an exception if either the download object or mime-type arguments are falsey"
+ );
+ }
+}
diff --git a/browser/components/downloads/test/unit/test_DownloadsViewableInternally.js b/browser/components/downloads/test/unit/test_DownloadsViewableInternally.js
new file mode 100644
index 0000000000..07925bc7d5
--- /dev/null
+++ b/browser/components/downloads/test/unit/test_DownloadsViewableInternally.js
@@ -0,0 +1,277 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PREF_SVG_DISABLED = "svg.disabled";
+const PREF_WEBP_ENABLED = "image.webp.enabled";
+const PREF_AVIF_ENABLED = "image.avif.enabled";
+const PDF_MIME = "application/pdf";
+const OCTET_MIME = "application/octet-stream";
+const XML_MIME = "text/xml";
+const SVG_MIME = "image/svg+xml";
+const AVIF_MIME = "image/avif";
+const WEBP_MIME = "image/webp";
+
+const { Integration } = ChromeUtils.importESModule(
+ "resource://gre/modules/Integration.sys.mjs"
+);
+const {
+ DownloadsViewableInternally,
+ PREF_ENABLED_TYPES,
+ PREF_BRANCH_WAS_REGISTERED,
+ PREF_BRANCH_PREVIOUS_ACTION,
+ PREF_BRANCH_PREVIOUS_ASK,
+} = ChromeUtils.importESModule(
+ "resource:///modules/DownloadsViewableInternally.sys.mjs"
+);
+
+/* global DownloadIntegration */
+Integration.downloads.defineESModuleGetter(
+ this,
+ "DownloadIntegration",
+ "resource://gre/modules/DownloadIntegration.sys.mjs"
+);
+
+const HandlerService = Cc[
+ "@mozilla.org/uriloader/handler-service;1"
+].getService(Ci.nsIHandlerService);
+const MIMEService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+
+function checkPreferInternal(mime, ext, expectedPreferInternal) {
+ const handler = MIMEService.getFromTypeAndExtension(mime, ext);
+ if (expectedPreferInternal) {
+ Assert.equal(
+ handler?.preferredAction,
+ Ci.nsIHandlerInfo.handleInternally,
+ `checking ${mime} preferredAction == handleInternally`
+ );
+ } else {
+ Assert.notEqual(
+ handler?.preferredAction,
+ Ci.nsIHandlerInfo.handleInternally,
+ `checking ${mime} preferredAction != handleInternally`
+ );
+ }
+}
+
+function shouldView(mime, ext) {
+ return DownloadIntegration.shouldViewDownloadInternally(mime, ext);
+}
+
+function checkShouldView(mime, ext, expectedShouldView) {
+ Assert.equal(
+ shouldView(mime, ext),
+ expectedShouldView,
+ `checking ${mime} shouldViewDownloadInternally`
+ );
+}
+
+function checkWasRegistered(ext, expectedWasRegistered) {
+ Assert.equal(
+ Services.prefs.getBoolPref(PREF_BRANCH_WAS_REGISTERED + ext, false),
+ expectedWasRegistered,
+ `checking ${ext} was registered pref`
+ );
+}
+
+function checkAll(mime, ext, expected) {
+ checkPreferInternal(mime, ext, expected && ext != "xml" && ext != "svg");
+ checkShouldView(mime, ext, expected);
+ if (ext != "xml" && ext != "svg") {
+ checkWasRegistered(ext, expected);
+ }
+}
+
+add_task(async function test_viewable_internally() {
+ Services.prefs.setCharPref(PREF_ENABLED_TYPES, "xml , svg,avif,webp");
+ Services.prefs.setBoolPref(PREF_SVG_DISABLED, false);
+ Services.prefs.setBoolPref(PREF_WEBP_ENABLED, true);
+ Services.prefs.setBoolPref(PREF_AVIF_ENABLED, true);
+
+ checkAll(XML_MIME, "xml", false);
+ checkAll(SVG_MIME, "svg", false);
+ checkAll(WEBP_MIME, "webp", false);
+ checkAll(AVIF_MIME, "avif", false);
+
+ DownloadsViewableInternally.register();
+
+ checkAll(XML_MIME, "xml", true);
+ checkAll(SVG_MIME, "svg", true);
+ checkAll(WEBP_MIME, "webp", true);
+ checkAll(AVIF_MIME, "avif", true);
+
+ // Remove webp so it won't be cleared
+ Services.prefs.clearUserPref(PREF_BRANCH_WAS_REGISTERED + "webp");
+
+ // Disable xml, avif and webp, check that avif becomes disabled
+ Services.prefs.setCharPref(PREF_ENABLED_TYPES, "svg");
+
+ // (XML is externally managed, and we just cleared the webp pref)
+ checkAll(XML_MIME, "xml", true);
+ checkPreferInternal(WEBP_MIME, "webp", true);
+
+ // Avif should be disabled
+ checkAll(AVIF_MIME, "avif", false);
+
+ // SVG shouldn't be cleared as it's still enabled
+ checkAll(SVG_MIME, "svg", true);
+
+ Assert.ok(
+ shouldView(PDF_MIME),
+ "application/pdf should be unaffected by pref"
+ );
+ Assert.ok(
+ shouldView(OCTET_MIME, "pdf"),
+ ".pdf should be accepted by extension"
+ );
+ Assert.ok(
+ shouldView(OCTET_MIME, "PDF"),
+ ".pdf should be detected case-insensitively"
+ );
+ Assert.ok(!shouldView(OCTET_MIME, "exe"), ".exe shouldn't be accepted");
+
+ Assert.ok(!shouldView(WEBP_MIME), "imave/webp should be disabled by pref");
+ Assert.ok(!shouldView(AVIF_MIME), "image/avif should be disabled by pref");
+
+ // Enable, check that everything is enabled again
+ Services.prefs.setCharPref(PREF_ENABLED_TYPES, "xml,svg,webp,avif");
+
+ checkAll(XML_MIME, "xml", true);
+ checkAll(SVG_MIME, "svg", true);
+ checkPreferInternal(WEBP_MIME, "webp", true);
+ checkPreferInternal(AVIF_MIME, "avif", true);
+
+ Assert.ok(
+ shouldView(PDF_MIME),
+ "application/pdf should be unaffected by pref"
+ );
+ Assert.ok(shouldView(XML_MIME), "text/xml should be enabled by pref");
+ Assert.ok(
+ shouldView("application/xml"),
+ "alternate MIME type application/xml should be accepted"
+ );
+ Assert.ok(
+ shouldView(OCTET_MIME, "xml"),
+ ".xml should be accepted by extension"
+ );
+
+ // Disable viewable internally, pre-set handlers.
+ Services.prefs.setCharPref(PREF_ENABLED_TYPES, "");
+
+ for (const [mime, ext, action, ask] of [
+ [XML_MIME, "xml", Ci.nsIHandlerInfo.useSystemDefault, true],
+ [SVG_MIME, "svg", Ci.nsIHandlerInfo.saveToDisk, true],
+ [WEBP_MIME, "webp", Ci.nsIHandlerInfo.saveToDisk, false],
+ ]) {
+ let handler = MIMEService.getFromTypeAndExtension(mime, ext);
+ handler.preferredAction = action;
+ handler.alwaysAskBeforeHandling = ask;
+
+ HandlerService.store(handler);
+ checkPreferInternal(mime, ext, false);
+
+ // Expect to read back the same values
+ handler = MIMEService.getFromTypeAndExtension(mime, ext);
+ Assert.equal(handler.preferredAction, action);
+ Assert.equal(handler.alwaysAskBeforeHandling, ask);
+ }
+
+ // Enable viewable internally, SVG and XML should not be replaced, WebP should be saved.
+ Services.prefs.setCharPref(PREF_ENABLED_TYPES, "svg,webp,xml");
+
+ Assert.equal(
+ Services.prefs.prefHasUserValue(PREF_BRANCH_PREVIOUS_ACTION + "svg"),
+ false,
+ "svg action should not be stored"
+ );
+ Assert.equal(
+ Services.prefs.prefHasUserValue(PREF_BRANCH_PREVIOUS_ASK + "svg"),
+ false,
+ "svg ask should not be stored"
+ );
+ Assert.equal(
+ Services.prefs.getIntPref(PREF_BRANCH_PREVIOUS_ACTION + "webp"),
+ Ci.nsIHandlerInfo.saveToDisk,
+ "webp action should be saved"
+ );
+ Assert.equal(
+ Services.prefs.getBoolPref(PREF_BRANCH_PREVIOUS_ASK + "webp"),
+ false,
+ "webp ask should be saved"
+ );
+
+ {
+ let handler = MIMEService.getFromTypeAndExtension(SVG_MIME, "svg");
+ Assert.equal(
+ handler.preferredAction,
+ Ci.nsIHandlerInfo.saveToDisk,
+ "svg action should be preserved"
+ );
+ Assert.equal(
+ !!handler.alwaysAskBeforeHandling,
+ true,
+ "svg ask should be preserved"
+ );
+ // Clean up
+ HandlerService.remove(handler);
+ handler = MIMEService.getFromTypeAndExtension(XML_MIME, "xml");
+ Assert.equal(
+ handler.preferredAction,
+ Ci.nsIHandlerInfo.useSystemDefault,
+ "xml action should be preserved"
+ );
+ Assert.equal(
+ !!handler.alwaysAskBeforeHandling,
+ true,
+ "xml ask should be preserved"
+ );
+ // Clean up
+ HandlerService.remove(handler);
+ }
+ // It should still be possible to view XML internally
+ checkShouldView(XML_MIME, "xml", true);
+
+ checkAll(SVG_MIME, "svg", true);
+ checkAll(WEBP_MIME, "webp", true);
+
+ // Disable SVG to test SVG enabled check (depends on the pref)
+ Services.prefs.setBoolPref(PREF_SVG_DISABLED, true);
+ checkAll(SVG_MIME, "svg", false);
+ Services.prefs.setBoolPref(PREF_SVG_DISABLED, false);
+ {
+ let handler = MIMEService.getFromTypeAndExtension(SVG_MIME, "svg");
+ handler.preferredAction = Ci.nsIHandlerInfo.saveToDisk;
+ handler.alwaysAskBeforeHandling = false;
+ HandlerService.store(handler);
+ }
+
+ checkAll(SVG_MIME, "svg", true);
+
+ // Test WebP enabled check (depends on the pref)
+ Services.prefs.setBoolPref(PREF_WEBP_ENABLED, false);
+ // Should have restored the settings from above
+ {
+ let handler = MIMEService.getFromTypeAndExtension(WEBP_MIME, "webp");
+ Assert.equal(handler.preferredAction, Ci.nsIHandlerInfo.saveToDisk);
+ Assert.equal(!!handler.alwaysAskBeforeHandling, false);
+ // Clean up
+ HandlerService.remove(handler);
+ }
+ checkAll(WEBP_MIME, "webp", false);
+
+ Services.prefs.setBoolPref(PREF_WEBP_ENABLED, true);
+ checkAll(WEBP_MIME, "webp", true);
+
+ Assert.ok(!shouldView(null, "pdf"), "missing MIME shouldn't be accepted");
+ Assert.ok(!shouldView(null, "xml"), "missing MIME shouldn't be accepted");
+ Assert.ok(!shouldView(OCTET_MIME), "unsupported MIME shouldn't be accepted");
+ Assert.ok(!shouldView(OCTET_MIME, "exe"), ".exe shouldn't be accepted");
+});
+
+registerCleanupFunction(() => {
+ // Clear all types to remove any saved values
+ Services.prefs.setCharPref(PREF_ENABLED_TYPES, "");
+ // Reset to the defaults
+ Services.prefs.clearUserPref(PREF_ENABLED_TYPES);
+ Services.prefs.clearUserPref(PREF_SVG_DISABLED);
+ Services.prefs.clearUserPref(PREF_WEBP_ENABLED);
+});
diff --git a/browser/components/downloads/test/unit/xpcshell.ini b/browser/components/downloads/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..9e67834c3e
--- /dev/null
+++ b/browser/components/downloads/test/unit/xpcshell.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+head = head.js
+firefox-appdir = browser
+skip-if = toolkit == 'android' # bug 1730213
+
+[test_DownloadLastDir_basics.js]
+[test_DownloadsCommon_getMimeInfo.js]
+[test_DownloadsCommon_isFileOfType.js]
+[test_DownloadsViewableInternally.js]