summaryrefslogtreecommitdiffstats
path: root/uriloader/exthandler/tests/unit
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /uriloader/exthandler/tests/unit
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'uriloader/exthandler/tests/unit')
-rw-r--r--uriloader/exthandler/tests/unit/handlers.json83
-rw-r--r--uriloader/exthandler/tests/unit/head.js82
-rw-r--r--uriloader/exthandler/tests/unit/mailcap2
-rw-r--r--uriloader/exthandler/tests/unit/test_badMIMEType.js29
-rw-r--r--uriloader/exthandler/tests/unit/test_defaults_handlerService.js159
-rw-r--r--uriloader/exthandler/tests/unit/test_downloads_improvements_migration.js244
-rw-r--r--uriloader/exthandler/tests/unit/test_filename_sanitize.js398
-rw-r--r--uriloader/exthandler/tests/unit/test_getFromTypeAndExtension.js17
-rw-r--r--uriloader/exthandler/tests/unit/test_getMIMEInfo_pdf.js30
-rw-r--r--uriloader/exthandler/tests/unit/test_getMIMEInfo_unknown_mime_type.js32
-rw-r--r--uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js65
-rw-r--r--uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js218
-rw-r--r--uriloader/exthandler/tests/unit/test_handlerService.js467
-rw-r--r--uriloader/exthandler/tests/unit/test_handlerService_store.js752
-rw-r--r--uriloader/exthandler/tests/unit/test_punycodeURIs.js126
-rw-r--r--uriloader/exthandler/tests/unit/xpcshell.ini40
16 files changed, 2744 insertions, 0 deletions
diff --git a/uriloader/exthandler/tests/unit/handlers.json b/uriloader/exthandler/tests/unit/handlers.json
new file mode 100644
index 0000000000..51ce581d83
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/handlers.json
@@ -0,0 +1,83 @@
+{
+ "defaultHandlersVersion": {
+ "en-US": 999
+ },
+ "mimeTypes": {
+ "example/type.handleinternally": {
+ "unknownProperty": "preserved",
+ "action": 3,
+ "extensions": ["example_one"]
+ },
+ "example/type.savetodisk": {
+ "action": 0,
+ "ask": true,
+ "handlers": [
+ {
+ "name": "Example Default Handler",
+ "uriTemplate": "https://www.example.com/?url=%s"
+ }
+ ],
+ "extensions": ["example_two", "example_three"]
+ },
+ "example/type.usehelperapp": {
+ "action": 2,
+ "ask": true,
+ "handlers": [
+ {
+ "name": "Example Default Handler",
+ "uriTemplate": "https://www.example.com/?url=%s"
+ },
+ {
+ "name": "Example Possible Handler One",
+ "uriTemplate": "http://www.example.com/?id=1&url=%s"
+ },
+ {
+ "name": "Example Possible Handler Two",
+ "uriTemplate": "http://www.example.com/?id=2&url=%s"
+ }
+ ],
+ "extensions": ["example_two", "example_three"]
+ },
+ "example/type.usesystemdefault": {
+ "action": 4,
+ "handlers": [
+ null,
+ {
+ "name": "Example Possible Handler",
+ "uriTemplate": "http://www.example.com/?url=%s"
+ }
+ ]
+ }
+ },
+ "schemes": {
+ "examplescheme.usehelperapp": {
+ "action": 2,
+ "ask": true,
+ "handlers": [
+ {
+ "name": "Example Default Handler",
+ "uriTemplate": "https://www.example.com/?url=%s"
+ },
+ {
+ "name": "Example Possible Handler One",
+ "uriTemplate": "http://www.example.com/?id=1&url=%s"
+ },
+ {
+ "name": "Example Possible Handler Two",
+ "uriTemplate": "http://www.example.com/?id=2&url=%s"
+ }
+ ]
+ },
+ "examplescheme.usesystemdefault": {
+ "action": 4,
+ "handlers": [
+ null,
+ {
+ "name": "Example Possible Handler",
+ "uriTemplate": "http://www.example.com/?url=%s"
+ }
+ ]
+ }
+ },
+ "isDownloadsImprovementsAlreadyMigrated": true
+}
diff --git a/uriloader/exthandler/tests/unit/head.js b/uriloader/exthandler/tests/unit/head.js
new file mode 100644
index 0000000000..2f65582ae9
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/head.js
@@ -0,0 +1,82 @@
+/* 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/. */
+
+/*
+ * Initialization for tests related to invoking external handler applications.
+ */
+
+"use strict";
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const { HandlerServiceTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/HandlerServiceTestUtils.sys.mjs"
+);
+var { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "gHandlerService",
+ "@mozilla.org/uriloader/handler-service;1",
+ "nsIHandlerService"
+);
+
+do_get_profile();
+
+let jsonPath = PathUtils.join(PathUtils.profileDir, "handlers.json");
+
+/**
+ * Unloads the nsIHandlerService data store, so the back-end file can be
+ * accessed or modified, and the new data will be loaded at the next access.
+ */
+let unloadHandlerStore = async function () {
+ // If this function is called before the nsIHandlerService instance has been
+ // initialized for the first time, the observer below will not be registered.
+ // We have to force initialization to prevent the function from stalling.
+ gHandlerService;
+
+ let promise = TestUtils.topicObserved("handlersvc-json-replace-complete");
+ Services.obs.notifyObservers(null, "handlersvc-json-replace");
+ await promise;
+};
+
+/**
+ * Unloads the data store and deletes it.
+ */
+let deleteHandlerStore = async function () {
+ await unloadHandlerStore();
+
+ await IOUtils.remove(jsonPath, { ignoreAbsent: true });
+
+ Services.prefs.clearUserPref("gecko.handlerService.defaultHandlersVersion");
+};
+
+/**
+ * Unloads the data store and replaces it with the test data file.
+ */
+let copyTestDataToHandlerStore = async function () {
+ await unloadHandlerStore();
+
+ await IOUtils.copy(do_get_file("handlers.json").path, jsonPath);
+
+ Services.prefs.setIntPref("gecko.handlerService.defaultHandlersVersion", 100);
+};
+
+/**
+ * Ensures the files are removed and the services unloaded when the tests end.
+ */
+registerCleanupFunction(async function test_terminate() {
+ await deleteHandlerStore();
+});
diff --git a/uriloader/exthandler/tests/unit/mailcap b/uriloader/exthandler/tests/unit/mailcap
new file mode 100644
index 0000000000..dc93ef8042
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/mailcap
@@ -0,0 +1,2 @@
+text/plain; cat '%s'; needsterminal
+text/plain; sed '%s'
diff --git a/uriloader/exthandler/tests/unit/test_badMIMEType.js b/uriloader/exthandler/tests/unit/test_badMIMEType.js
new file mode 100644
index 0000000000..49c5e8d848
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_badMIMEType.js
@@ -0,0 +1,29 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * 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/. */
+
+function run_test() {
+ // "text/plain" has an 0xFF character appended to it. This means it's an
+ // invalid string, which is tricky to enter using a text editor (I used
+ // emacs' hexl-mode). It also means an ordinary text editor might drop it
+ // or convert it to something that *is* valid (in UTF8). So we measure
+ // its length to make sure this hasn't happened.
+ var badMimeType = "text/plainÿ";
+ Assert.equal(badMimeType.length, 11);
+ try {
+ Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getFromTypeAndExtension(badMimeType, "txt");
+ } catch (e) {
+ if (
+ !(e instanceof Ci.nsIException) ||
+ e.result != Cr.NS_ERROR_NOT_AVAILABLE
+ ) {
+ throw e;
+ }
+ // This is an expected exception, thrown if the type can't be determined
+ }
+ // Not crashing is good enough
+ Assert.equal(true, true);
+}
diff --git a/uriloader/exthandler/tests/unit/test_defaults_handlerService.js b/uriloader/exthandler/tests/unit/test_defaults_handlerService.js
new file mode 100644
index 0000000000..47fd18a642
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_defaults_handlerService.js
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "gExternalProtocolService",
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ "nsIExternalProtocolService"
+);
+ChromeUtils.defineESModuleGetters(this, {
+ kHandlerList: "resource://gre/modules/handlers/HandlerList.sys.mjs",
+});
+
+add_task(async function test_check_defaults_get_added() {
+ let protocols = Object.keys(kHandlerList.default.schemes);
+ for (let protocol of protocols) {
+ let protocolHandlerCount =
+ kHandlerList.default.schemes[protocol].handlers.length;
+ Assert.ok(
+ gHandlerService.wrappedJSObject._store.data.schemes[protocol].stubEntry,
+ `Expect stub for ${protocol}`
+ );
+ let info = gExternalProtocolService.getProtocolHandlerInfo(protocol, {});
+ Assert.ok(
+ info,
+ `Should be able to get protocol handler info for ${protocol}`
+ );
+ let handlers = Array.from(
+ info.possibleApplicationHandlers.enumerate(Ci.nsIHandlerApp)
+ );
+ handlers = handlers.filter(h => h instanceof Ci.nsIWebHandlerApp);
+ Assert.equal(
+ handlers.length,
+ protocolHandlerCount,
+ `Default web handlers for ${protocol} should match`
+ );
+ let { alwaysAskBeforeHandling, preferredAction } = info;
+ // Actually store something, pretending there was a change:
+ let infoToWrite = gExternalProtocolService.getProtocolHandlerInfo(
+ protocol,
+ {}
+ );
+ gHandlerService.store(infoToWrite);
+ ok(
+ !gHandlerService.wrappedJSObject._store.data.schemes[protocol].stubEntry,
+ "Expect stub entry info to go away"
+ );
+
+ let newInfo = gExternalProtocolService.getProtocolHandlerInfo(protocol, {});
+ Assert.equal(
+ alwaysAskBeforeHandling,
+ newInfo.alwaysAskBeforeHandling,
+ protocol + " - always ask shouldn't change"
+ );
+ Assert.equal(
+ preferredAction,
+ newInfo.preferredAction,
+ protocol + " - preferred action shouldn't change"
+ );
+ await deleteHandlerStore();
+ }
+});
+
+add_task(async function test_check_default_modification() {
+ Assert.ok(
+ true,
+ JSON.stringify(gHandlerService.wrappedJSObject._store.data.schemes.mailto)
+ );
+ Assert.ok(
+ gHandlerService.wrappedJSObject._store.data.schemes.mailto.stubEntry,
+ "Expect stub for mailto"
+ );
+ let mailInfo = gExternalProtocolService.getProtocolHandlerInfo("mailto", {});
+ mailInfo.alwaysAskBeforeHandling = false;
+ mailInfo.preferredAction = Ci.nsIHandlerInfo.useSystemDefault;
+ gHandlerService.store(mailInfo);
+ Assert.ok(
+ !gHandlerService.wrappedJSObject._store.data.schemes.mailto.stubEntry,
+ "Stub entry should be removed immediately."
+ );
+ let newMail = gExternalProtocolService.getProtocolHandlerInfo("mailto", {});
+ Assert.equal(newMail.preferredAction, Ci.nsIHandlerInfo.useSystemDefault);
+ Assert.equal(newMail.alwaysAskBeforeHandling, false);
+ await deleteHandlerStore();
+});
+
+add_task(async function test_migrations() {
+ const kTestData = [
+ ["A", "http://compose.mail.yahoo.co.jp/ym/Compose?To=%s"],
+ ["B", "http://www.inbox.lv/rfc2368/?value=%s"],
+ ["C", "http://poczta.interia.pl/mh/?mailto=%s"],
+ ["D", "http://win.mail.ru/cgi-bin/sentmsg?mailto=%s"],
+ ];
+ // Set up the test handlers. This doesn't use prefs like the previous test,
+ // because we now refuse to import insecure handler prefs. They can only
+ // exist if they were added into the handler store before this restriction
+ // was created (bug 1526890).
+ gHandlerService.wrappedJSObject._injectDefaultProtocolHandlers();
+ let handler = gExternalProtocolService.getProtocolHandlerInfo("mailto");
+ while (handler.possibleApplicationHandlers.length) {
+ handler.possibleApplicationHandlers.removeElementAt(0);
+ }
+ for (let [name, uriTemplate] of kTestData) {
+ let app = Cc["@mozilla.org/uriloader/web-handler-app;1"].createInstance(
+ Ci.nsIWebHandlerApp
+ );
+ app.uriTemplate = uriTemplate;
+ app.name = name;
+ handler.possibleApplicationHandlers.appendElement(app);
+ }
+ gHandlerService.store(handler);
+
+ // Now migrate them:
+ Services.prefs.setCharPref("browser.handlers.migrations", "blah,secure-mail");
+ gHandlerService.wrappedJSObject._migrateProtocolHandlersIfNeeded();
+
+ // Now check the result:
+ handler = gExternalProtocolService.getProtocolHandlerInfo("mailto");
+
+ let expectedURIs = new Set([
+ "https://mail.yahoo.co.jp/compose/?To=%s",
+ "https://mail.inbox.lv/compose?to=%s",
+ "https://poczta.interia.pl/mh/?mailto=%s",
+ "https://e.mail.ru/cgi-bin/sentmsg?mailto=%s",
+ ]);
+
+ let possibleApplicationHandlers = Array.from(
+ handler.possibleApplicationHandlers.enumerate(Ci.nsIWebHandlerApp)
+ );
+ // Set iterators are stable to deletion, so this works:
+ for (let expected of expectedURIs) {
+ for (let app of possibleApplicationHandlers) {
+ if (app instanceof Ci.nsIWebHandlerApp && app.uriTemplate == expected) {
+ Assert.ok(true, "Found handler with URI " + expected);
+ // ... even when we remove items.
+ expectedURIs.delete(expected);
+ break;
+ }
+ }
+ }
+ Assert.equal(expectedURIs.size, 0, "Should have seen all the expected URIs.");
+
+ for (let app of possibleApplicationHandlers) {
+ if (app instanceof Ci.nsIWebHandlerApp) {
+ Assert.ok(
+ !kTestData.some(n => n[1] == app.uriTemplate),
+ "Should not be any of the original handlers"
+ );
+ }
+ }
+
+ Assert.ok(
+ !handler.preferredApplicationHandler,
+ "Shouldn't have preferred handler initially."
+ );
+ await deleteHandlerStore();
+});
diff --git a/uriloader/exthandler/tests/unit/test_downloads_improvements_migration.js b/uriloader/exthandler/tests/unit/test_downloads_improvements_migration.js
new file mode 100644
index 0000000000..49e4493b23
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_downloads_improvements_migration.js
@@ -0,0 +1,244 @@
+/* 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/. */
+
+const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
+ Ci.nsIHandlerService
+);
+
+const mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+
+const { Integration } = ChromeUtils.importESModule(
+ "resource://gre/modules/Integration.sys.mjs"
+);
+
+/* global DownloadIntegration */
+Integration.downloads.defineESModuleGetter(
+ this,
+ "DownloadIntegration",
+ "resource://gre/modules/DownloadIntegration.sys.mjs"
+);
+
+/**
+ * Tests that the migration runs and that only
+ * files with preferredAction alwaysAsk are updated.
+ */
+add_task(async function test_migration() {
+ // Create mock implementation of shouldDownloadInternally for test case
+ let oldShouldViewDownloadInternally =
+ DownloadIntegration.shouldViewDownloadInternally;
+ DownloadIntegration.shouldViewDownloadInternally = (mimeType, extension) => {
+ let downloadTypesViewableInternally = [
+ {
+ extension: "pdf",
+ mimeTypes: ["application/pdf"],
+ },
+ {
+ extension: "webp",
+ mimeTypes: ["image/webp"],
+ },
+ ];
+
+ for (const mockHandler of downloadTypesViewableInternally) {
+ if (mockHandler.mimeTypes.includes(mimeType)) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ registerCleanupFunction(async function () {
+ Services.prefs.clearUserPref(
+ "browser.download.improvements_to_download_panel"
+ );
+ DownloadIntegration.shouldViewDownloadInternally =
+ oldShouldViewDownloadInternally;
+ });
+
+ // For setup, set pref to false. Will be enabled later.
+ Services.prefs.setBoolPref(
+ "browser.download.improvements_to_download_panel",
+ false
+ );
+
+ // Plain text file
+ let txtHandlerInfo = mimeSvc.getFromTypeAndExtension("text/plain", "txt");
+ txtHandlerInfo.preferredAction = Ci.nsIHandlerInfo.alwaysAsk;
+ txtHandlerInfo.alwaysAskBeforeHandling = true;
+ // PDF file
+ let pdfHandlerInfo = mimeSvc.getFromTypeAndExtension(
+ "application/pdf",
+ "pdf"
+ );
+ pdfHandlerInfo.preferredAction = Ci.nsIHandlerInfo.alwaysAsk;
+ pdfHandlerInfo.alwaysAskBeforeHandling = true;
+ // WebP file
+ let webpHandlerInfo = mimeSvc.getFromTypeAndExtension("image/webp", "webp");
+ webpHandlerInfo.preferredAction = Ci.nsIHandlerInfo.useSystemDefault;
+ webpHandlerInfo.alwaysAskBeforeHandling = false;
+
+ handlerSvc.store(txtHandlerInfo);
+ handlerSvc.store(pdfHandlerInfo);
+ handlerSvc.store(webpHandlerInfo);
+
+ Services.prefs.setBoolPref(
+ "browser.download.improvements_to_download_panel",
+ true
+ );
+ gHandlerService.wrappedJSObject._migrateDownloadsImprovementsIfNeeded();
+
+ txtHandlerInfo = mimeSvc.getFromTypeAndExtension("text/plain", "txt");
+ pdfHandlerInfo = mimeSvc.getFromTypeAndExtension("application/pdf", "pdf");
+ webpHandlerInfo = mimeSvc.getFromTypeAndExtension("image/webp", "webp");
+ let data = gHandlerService.wrappedJSObject._store.data;
+ Assert.equal(
+ data.isDownloadsImprovementsAlreadyMigrated,
+ true,
+ "isDownloadsImprovementsAlreadyMigrated should be set to true"
+ );
+ Assert.equal(
+ pdfHandlerInfo.preferredAction,
+ Ci.nsIHandlerInfo.handleInternally,
+ "application/pdf - preferredAction should be handleInternally"
+ );
+ Assert.equal(
+ pdfHandlerInfo.alwaysAskBeforeHandling,
+ false,
+ "application/pdf - alwaysAskBeforeHandling should be false"
+ );
+ Assert.equal(
+ webpHandlerInfo.preferredAction,
+ Ci.nsIHandlerInfo.useSystemDefault,
+ "image/webp - preferredAction should be useSystemDefault"
+ );
+ Assert.equal(
+ webpHandlerInfo.alwaysAskBeforeHandling,
+ false,
+ "image/webp - alwaysAskBeforeHandling should be false"
+ );
+ Assert.equal(
+ txtHandlerInfo.preferredAction,
+ Ci.nsIHandlerInfo.saveToDisk,
+ "text/plain - preferredAction should be saveToDisk"
+ );
+ Assert.equal(
+ txtHandlerInfo.alwaysAskBeforeHandling,
+ false,
+ "text/plain - alwaysAskBeforeHandling should be false"
+ );
+});
+
+/**
+ * Tests that the migration does not run if the migration was already run.
+ */
+add_task(async function test_migration_already_run() {
+ let data = gHandlerService.wrappedJSObject._store.data;
+ data.isDownloadsImprovementsAlreadyMigrated = true;
+
+ // Plain text file
+ let txtHandlerInfo = mimeSvc.getFromTypeAndExtension("text/plain", "txt");
+ txtHandlerInfo.preferredAction = Ci.nsIHandlerInfo.alwaysAsk;
+ txtHandlerInfo.alwaysAskBeforeHandling = true;
+ // PDF file
+ let pdfHandlerInfo = mimeSvc.getFromTypeAndExtension(
+ "application/pdf",
+ "pdf"
+ );
+ pdfHandlerInfo.preferredAction = Ci.nsIHandlerInfo.alwaysAsk;
+ pdfHandlerInfo.alwaysAskBeforeHandling = true;
+
+ handlerSvc.store(txtHandlerInfo);
+ handlerSvc.store(pdfHandlerInfo);
+
+ gHandlerService.wrappedJSObject._migrateDownloadsImprovementsIfNeeded();
+
+ txtHandlerInfo = mimeSvc.getFromTypeAndExtension("text/plain", "txt");
+ pdfHandlerInfo = mimeSvc.getFromTypeAndExtension("application/pdf", "pdf");
+ data = gHandlerService.wrappedJSObject._store.data;
+ Assert.equal(
+ pdfHandlerInfo.preferredAction,
+ Ci.nsIHandlerInfo.alwaysAsk,
+ "application/pdf - preferredAction should be alwaysAsk"
+ );
+ Assert.equal(
+ pdfHandlerInfo.alwaysAskBeforeHandling,
+ true,
+ "application/pdf - alwaysAskBeforeHandling should be true"
+ );
+ Assert.equal(
+ txtHandlerInfo.preferredAction,
+ Ci.nsIHandlerInfo.alwaysAsk,
+ "text/plain - preferredAction should be alwaysAsk"
+ );
+ Assert.equal(
+ txtHandlerInfo.alwaysAskBeforeHandling,
+ true,
+ "text/plain - alwaysAskBeforeHandling should be true"
+ );
+});
+
+/**
+ * Test migration of SVG and XML info.
+ */
+add_task(async function test_migration_xml_svg() {
+ let data = gHandlerService.wrappedJSObject._store.data;
+ // Plain text file
+ let txtHandlerInfo = mimeSvc.getFromTypeAndExtension("text/plain", "txt");
+ txtHandlerInfo.preferredAction = Ci.nsIHandlerInfo.alwaysAsk;
+ txtHandlerInfo.alwaysAskBeforeHandling = true;
+ // SVG file
+ let svgHandlerInfo = mimeSvc.getFromTypeAndExtension("image/svg+xml", "svg");
+ svgHandlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
+ svgHandlerInfo.alwaysAskBeforeHandling = false;
+ // XML file
+ let xmlHandlerInfo = mimeSvc.getFromTypeAndExtension("text/xml", "xml");
+ xmlHandlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
+ xmlHandlerInfo.alwaysAskBeforeHandling = false;
+
+ handlerSvc.store(txtHandlerInfo);
+ handlerSvc.store(svgHandlerInfo);
+ handlerSvc.store(xmlHandlerInfo);
+
+ gHandlerService.wrappedJSObject._migrateSVGXMLIfNeeded();
+
+ txtHandlerInfo = mimeSvc.getFromTypeAndExtension("text/plain", "txt");
+ svgHandlerInfo = mimeSvc.getFromTypeAndExtension("image/svg+xml", "svg");
+ xmlHandlerInfo = mimeSvc.getFromTypeAndExtension("text/xml", "xml");
+ data = gHandlerService.wrappedJSObject._store.data;
+ Assert.equal(
+ svgHandlerInfo.preferredAction,
+ Ci.nsIHandlerInfo.saveToDisk,
+ "image/svg+xml - preferredAction should be saveToDisk"
+ );
+ Assert.equal(
+ svgHandlerInfo.alwaysAskBeforeHandling,
+ false,
+ "image/svg+xml - alwaysAskBeforeHandling should be false"
+ );
+ Assert.equal(
+ xmlHandlerInfo.preferredAction,
+ Ci.nsIHandlerInfo.saveToDisk,
+ "text/xml - preferredAction should be saveToDisk"
+ );
+ Assert.equal(
+ xmlHandlerInfo.alwaysAskBeforeHandling,
+ false,
+ "text/xml - alwaysAskBeforeHandling should be false"
+ );
+ Assert.equal(
+ txtHandlerInfo.preferredAction,
+ Ci.nsIHandlerInfo.alwaysAsk,
+ "text/plain - preferredAction should be alwaysAsk"
+ );
+ Assert.equal(
+ txtHandlerInfo.alwaysAskBeforeHandling,
+ true,
+ "text/plain - alwaysAskBeforeHandling should be true"
+ );
+
+ ok(
+ data.isSVGXMLAlreadyMigrated,
+ "Should have stored migration state on the data object."
+ );
+});
diff --git a/uriloader/exthandler/tests/unit/test_filename_sanitize.js b/uriloader/exthandler/tests/unit/test_filename_sanitize.js
new file mode 100644
index 0000000000..d8ab12c266
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_filename_sanitize.js
@@ -0,0 +1,398 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test verifies that
+// nsIMIMEService.validateFileNameForSaving sanitizes filenames
+// properly with different flags.
+
+"use strict";
+
+add_task(async function validate_filename_method() {
+ let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+
+ function checkFilename(filename, flags, mime = "image/png") {
+ return mimeService.validateFileNameForSaving(filename, mime, flags);
+ }
+
+ Assert.equal(checkFilename("basicfile.png", 0), "basicfile.png");
+ Assert.equal(checkFilename(" whitespace.png ", 0), "whitespace.png");
+ Assert.equal(
+ checkFilename(" .whitespaceanddots.png...", 0),
+ "whitespaceanddots.png"
+ );
+ Assert.equal(
+ checkFilename(" \u00a0 \u00a0 extrawhitespace.png \u00a0 \u00a0 ", 0),
+ "extrawhitespace.png"
+ );
+ Assert.equal(
+ checkFilename(" filename with whitespace.png ", 0),
+ "filename with whitespace.png"
+ );
+ Assert.equal(checkFilename("\\path.png", 0), "_path.png");
+ Assert.equal(
+ checkFilename("\\path*and/$?~file.png", 0),
+ "_path and_$ ~file.png"
+ );
+ Assert.equal(
+ checkFilename(" \u180e whit\u180ee.png \u180e", 0),
+ "whit\u180ee.png"
+ );
+ Assert.equal(checkFilename("簡単簡単簡単", 0), "簡単簡単簡単.png");
+ Assert.equal(checkFilename(" happy\u061c\u2069.png", 0), "happy__.png");
+ Assert.equal(
+ checkFilename("12345678".repeat(31) + "abcdefgh.png", 0),
+ "12345678".repeat(31) + "ab.png"
+ );
+ Assert.equal(
+ checkFilename("簡単".repeat(41) + ".png", 0),
+ "簡単".repeat(41) + ".png"
+ );
+ Assert.equal(
+ checkFilename("a" + "簡単".repeat(42) + ".png", 0),
+ "a" + "簡単".repeat(40) + "簡.png"
+ );
+ Assert.equal(
+ checkFilename("a" + "簡単".repeat(56) + ".png", 0),
+ "a" + "簡単".repeat(40) + ".png"
+ );
+ Assert.equal(checkFilename("café.png", 0), "café.png");
+ Assert.equal(
+ checkFilename("café".repeat(50) + ".png", 0),
+ "café".repeat(50) + ".png"
+ );
+ Assert.equal(
+ checkFilename("café".repeat(51) + ".png", 0),
+ "café".repeat(49) + "caf.png"
+ );
+
+ Assert.equal(
+ checkFilename("\u{100001}\u{100002}.png", 0),
+ "\u{100001}\u{100002}.png"
+ );
+ Assert.equal(
+ checkFilename("\u{100001}\u{100002}".repeat(31) + ".png", 0),
+ "\u{100001}\u{100002}".repeat(31) + ".png"
+ );
+ Assert.equal(
+ checkFilename("\u{100001}\u{100002}".repeat(32) + ".png", 0),
+ "\u{100001}\u{100002}".repeat(30) + "\u{100001}.png"
+ );
+
+ Assert.equal(
+ checkFilename("noextensionfile".repeat(16), 0),
+ "noextensionfile".repeat(16) + ".png"
+ );
+ Assert.equal(
+ checkFilename("noextensionfile".repeat(17), 0),
+ "noextensionfile".repeat(16) + "noextensio.png"
+ );
+ Assert.equal(
+ checkFilename("noextensionfile".repeat(16) + "noextensionfil.", 0),
+ "noextensionfile".repeat(16) + "noextensio.png"
+ );
+
+ Assert.equal(checkFilename(" first .png ", 0), "first .png");
+ Assert.equal(
+ checkFilename(
+ " second .png ",
+ mimeService.VALIDATE_DONT_COLLAPSE_WHITESPACE
+ ),
+ "second .png"
+ );
+
+ // For whatever reason, the Android mime handler accepts the .jpeg
+ // extension for image/png, so skip this test there.
+ if (AppConstants.platform != "android") {
+ Assert.equal(checkFilename("thi/*rd.jpeg", 0), "thi_ rd.png");
+ }
+
+ Assert.equal(
+ checkFilename("f*\\ourth file.jpg", mimeService.VALIDATE_SANITIZE_ONLY),
+ "f _ourth file.jpg"
+ );
+ Assert.equal(
+ checkFilename(
+ "f*\\ift h.jpe*\\g",
+ mimeService.VALIDATE_SANITIZE_ONLY |
+ mimeService.VALIDATE_DONT_COLLAPSE_WHITESPACE
+ ),
+ "f _ift h.jpe _g"
+ );
+ Assert.equal(checkFilename("sixth.j pe/*g", 0), "sixth.png");
+
+ let repeatStr = "12345678".repeat(31);
+ Assert.equal(
+ checkFilename(
+ repeatStr + "seventh.png",
+ mimeService.VALIDATE_DONT_TRUNCATE
+ ),
+ repeatStr + "seventh.png"
+ );
+ Assert.equal(
+ checkFilename(repeatStr + "seventh.png", 0),
+ repeatStr + "se.png"
+ );
+
+ // no filename, so index is used by default.
+ Assert.equal(checkFilename(".png", 0), "png.png");
+
+ // sanitization only, so Untitled is not added, but initial period is stripped.
+ Assert.equal(
+ checkFilename(".png", mimeService.VALIDATE_SANITIZE_ONLY),
+ "png"
+ );
+
+ // correct .png extension is applied.
+ Assert.equal(checkFilename(".butterpecan.icecream", 0), "butterpecan.png");
+
+ // sanitization only, so extension is not modified, but initial period is stripped.
+ Assert.equal(
+ checkFilename(".butterpecan.icecream", mimeService.VALIDATE_SANITIZE_ONLY),
+ "butterpecan.icecream"
+ );
+
+ let ext = ".fairlyLongExtension";
+ Assert.equal(
+ checkFilename(repeatStr + ext, mimeService.VALIDATE_SANITIZE_ONLY),
+ repeatStr.substring(0, 254 - ext.length) + ext
+ );
+
+ ext = "lo%?n/ginvalid? ch\\ars";
+ Assert.equal(
+ checkFilename(repeatStr + ext, mimeService.VALIDATE_SANITIZE_ONLY),
+ repeatStr + "lo% n_"
+ );
+
+ ext = ".long/invalid%? ch\\ars";
+ Assert.equal(
+ checkFilename(repeatStr + ext, mimeService.VALIDATE_SANITIZE_ONLY),
+ repeatStr.substring(0, 233) + ".long_invalid% ch_ars"
+ );
+
+ Assert.equal(
+ checkFilename("test_テスト_T\x83E\\S\x83T.png", 0),
+ "test_テスト_T E_S T.png"
+ );
+ Assert.equal(
+ checkFilename("test_テスト_T\x83E\\S\x83T.pテ\x83ng", 0),
+ "test_テスト_T E_S T.png"
+ );
+
+ // Check we don't invalidate surrogate pairs when trimming.
+ Assert.equal(checkFilename("test😀", 0, ""), "test😀");
+ Assert.equal(checkFilename("test😀😀", 0, ""), "test😀😀");
+
+ // Now check some media types
+ Assert.equal(
+ mimeService.validateFileNameForSaving("video.ogg", "video/ogg", 0),
+ "video.ogg",
+ "video.ogg"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("video.ogv", "video/ogg", 0),
+ "video.ogv",
+ "video.ogv"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("video.ogt", "video/ogg", 0),
+ "video.ogv",
+ "video.ogt"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving("audio.mp3", "audio/mpeg", 0),
+ "audio.mp3",
+ "audio.mp3"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("audio.mpega", "audio/mpeg", 0),
+ "audio.mpega",
+ "audio.mpega"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("audio.mp2", "audio/mpeg", 0),
+ "audio.mp2",
+ "audio.mp2"
+ );
+
+ let expected = "audio.mp3";
+ if (AppConstants.platform == "linux") {
+ expected = "audio.mpga";
+ } else if (AppConstants.platform == "android") {
+ expected = "audio.mp4";
+ }
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving("audio.mp4", "audio/mpeg", 0),
+ expected,
+ "audio.mp4"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving("sound.m4a", "audio/mp4", 0),
+ "sound.m4a",
+ "sound.m4a"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("sound.m4b", "audio/mp4", 0),
+ AppConstants.platform == "android" ? "sound.m4a" : "sound.m4b",
+ "sound.m4b"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("sound.m4c", "audio/mp4", 0),
+ AppConstants.platform == "macosx" ? "sound.mp4" : "sound.m4a",
+ "sound.mpc"
+ );
+
+ // This has a long filename with a 13 character extension. The end of the filename should be
+ // cropped to fit into 255 bytes.
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ "라이브9.9만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장24%102 000원 브랜드데이 앵콜 🎁 1.등 유산균 컬처렐 특가!",
+ "text/unknown",
+ mimeService.VALIDATE_SANITIZE_ONLY
+ ),
+ "라이브9.9만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 .등 유산균 컬처렐 특가!",
+ "very long filename with extension"
+ );
+
+ // This filename has a very long extension, almost the entire filename.
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ "라이브9.9만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장24%102 000원 브랜드데이 앵콜 🎁 1등 유산균 컬처렐 특가!",
+ "text/unknown",
+ mimeService.VALIDATE_SANITIZE_ONLY
+ ),
+ "라이브9",
+ "another very long filename with long extension"
+ );
+
+ // This filename is cropped at 254 bytes.
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ ".라이브99만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장24%102 000원 브랜드데이 앵콜 🎁 1등 유산균 컬처렐 특가!",
+ "text/unknown",
+ mimeService.VALIDATE_SANITIZE_ONLY
+ ),
+ "라이브99만 시청컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장컬처렐 다이제스티브 3박스 - 3박스 더 (뚱랑이 굿즈 증정) - 선물용 쇼핑백 2장24%102 000원 브랜드데",
+ "very filename with extension only"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving("filename.LNK", "text/unknown", 0),
+ "filename.LNK.download",
+ "filename.LNK"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("filename.local", "text/unknown", 0),
+ "filename.local.download",
+ "filename.local"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("filename.url", "text/unknown", 0),
+ "filename.url.download",
+ "filename.url"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("filename.URl", "text/unknown", 0),
+ "filename.URl.download",
+ "filename.URl"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("filename.scf", "text/unknown", 0),
+ "filename.scf.download",
+ "filename.scf"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving("filename.sCF", "text/unknown", 0),
+ "filename.sCF.download",
+ "filename.sCF"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving("filename.lnk\n", "text/unknown", 0),
+ "filename.lnk.download",
+ "filename.lnk with newline"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ "filename.lnk\n ",
+ "text/unknown",
+ 0
+ ),
+ "filename.lnk.download",
+ "filename.lnk with newline"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ "filename.\n\t lnk",
+ "text/unknown",
+ 0
+ ),
+ "filename. lnk",
+ "filename.lnk with space and newline"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ "filename.local\u180e\u180e\u180e",
+ "text/unknown",
+ 0
+ ),
+ "filename.local.download",
+ "filename.lnk with vowel separators"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ "filename.LNK",
+ "text/unknown",
+ mimeService.VALIDATE_SANITIZE_ONLY
+ ),
+ "filename.LNK.download",
+ "filename.LNK sanitize only"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ "filename.LNK\n",
+ "text/unknown",
+ mimeService.VALIDATE_ALLOW_INVALID_FILENAMES
+ ),
+ "filename.LNK",
+ "filename.LNK allow invalid"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ "filename.URL\n",
+ "text/unknown",
+ mimeService.VALIDATE_SANITIZE_ONLY |
+ mimeService.VALIDATE_ALLOW_INVALID_FILENAMES
+ ),
+ "filename.URL",
+ "filename.URL allow invalid, sanitize only"
+ );
+
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ "filename.desktop",
+ "text/unknown",
+ mimeService.VALIDATE_SANITIZE_ONLY
+ ),
+ "filename.desktop.download",
+ "filename.desktop sanitize only"
+ );
+ Assert.equal(
+ mimeService.validateFileNameForSaving(
+ "filename.DESKTOP\n",
+ "text/unknown",
+ mimeService.VALIDATE_SANITIZE_ONLY |
+ mimeService.VALIDATE_ALLOW_INVALID_FILENAMES
+ ),
+ "filename.DESKTOP",
+ "filename.DESKTOP allow invalid, sanitize only"
+ );
+});
diff --git a/uriloader/exthandler/tests/unit/test_getFromTypeAndExtension.js b/uriloader/exthandler/tests/unit/test_getFromTypeAndExtension.js
new file mode 100644
index 0000000000..6f4fe52a49
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_getFromTypeAndExtension.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_utf8_extension() {
+ const mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let someMIME = mimeService.getFromTypeAndExtension(
+ "application/x-nonsense",
+ ".тест"
+ );
+ Assert.stringContains(someMIME.description, "тест");
+ // primary extension isn't set on macOS or android, see bug 1721181
+ if (AppConstants.platform != "macosx" && AppConstants.platform != "android") {
+ Assert.equal(someMIME.primaryExtension, ".тест");
+ }
+});
diff --git a/uriloader/exthandler/tests/unit/test_getMIMEInfo_pdf.js b/uriloader/exthandler/tests/unit/test_getMIMEInfo_pdf.js
new file mode 100644
index 0000000000..03b0cea25e
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_getMIMEInfo_pdf.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "gMIMEService",
+ "@mozilla.org/mime;1",
+ "nsIMIMEService"
+);
+
+// PDF files should always have a generic description instead
+// of relying on what is registered with the Operating System.
+add_task(async function test_check_unknown_mime_type() {
+ const mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let pdfType = mimeService.getTypeFromExtension("pdf");
+ Assert.equal(pdfType, "application/pdf");
+ let extension = mimeService.getPrimaryExtension("application/pdf", "");
+ Assert.equal(extension, "pdf", "Expect pdf extension when given mime");
+ let mimeInfo = gMIMEService.getFromTypeAndExtension("", "pdf");
+ let stringBundle = Services.strings.createBundle(
+ "chrome://mozapps/locale/downloads/unknownContentType.properties"
+ );
+ Assert.equal(
+ mimeInfo.description,
+ stringBundle.GetStringFromName("pdfExtHandlerDescription"),
+ "PDF has generic description"
+ );
+});
diff --git a/uriloader/exthandler/tests/unit/test_getMIMEInfo_unknown_mime_type.js b/uriloader/exthandler/tests/unit/test_getMIMEInfo_unknown_mime_type.js
new file mode 100644
index 0000000000..9beef9d9c5
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_getMIMEInfo_unknown_mime_type.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Zip files can be opened by Windows explorer, so we should always be able to
+// determine a description and default handler for them. However, things can
+// get messy if they are sent to us with a mime type other than what Windows
+// considers the "right" mimetype (application/x-zip-compressed), like
+// application/zip, which is what most places (IANA, macOS, probably all linux
+// distros, Apache, etc.) think is the "right" mimetype.
+add_task(async function test_check_unknown_mime_type() {
+ const mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let zipType = mimeService.getTypeFromExtension("zip");
+ Assert.equal(zipType, "application/x-zip-compressed");
+ try {
+ let extension = mimeService.getPrimaryExtension("application/zip", "");
+ Assert.equal(
+ extension,
+ "zip",
+ "Expect our own info to provide an extension for zip files."
+ );
+ } catch (ex) {
+ Assert.ok(false, "We shouldn't throw when getting zip info.");
+ }
+ let found = {};
+ let mimeInfo = mimeService.getMIMEInfoFromOS("application/zip", "zip", found);
+ Assert.ok(
+ mimeInfo.hasDefaultHandler,
+ "Should have a default app for zip files"
+ );
+});
diff --git a/uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js
new file mode 100644
index 0000000000..7202db58de
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_ext_to_type_mapping.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * 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 for bug 508030 <https://bugzilla.mozilla.org/show_bug.cgi?id=508030>:
+ * nsIMIMEService.getTypeFromExtension fails to find a match in the
+ * "ext-to-type-mapping" category if the provided extension is not lowercase.
+ */
+function run_test() {
+ // --- Common services ---
+
+ const mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+
+ const categoryManager = Services.catMan;
+
+ // --- Test procedure ---
+
+ const kTestExtension = "testextension";
+ const kTestExtensionMixedCase = "testExtensIon";
+ const kTestMimeType = "application/x-testextension";
+
+ // Ensure that the test extension is not initially recognized by the operating
+ // system or the "ext-to-type-mapping" category.
+ try {
+ // Try and get the MIME type associated with the extension.
+ mimeService.getTypeFromExtension(kTestExtension);
+ // The line above should have thrown an exception.
+ do_throw("nsIMIMEService.getTypeFromExtension succeeded unexpectedly");
+ } catch (e) {
+ if (
+ !(e instanceof Ci.nsIException) ||
+ e.result != Cr.NS_ERROR_NOT_AVAILABLE
+ ) {
+ throw e;
+ }
+ // This is an expected exception, thrown if the type can't be determined.
+ // Any other exception would cause the test to fail.
+ }
+
+ // Add a temporary category entry mapping the extension to the MIME type.
+ categoryManager.addCategoryEntry(
+ "ext-to-type-mapping",
+ kTestExtension,
+ kTestMimeType,
+ false,
+ true
+ );
+
+ // Check that the mapping is recognized in the simple case.
+ var type = mimeService.getTypeFromExtension(kTestExtension);
+ Assert.equal(type, kTestMimeType);
+
+ // Check that the mapping is recognized even if the extension has mixed case.
+ type = mimeService.getTypeFromExtension(kTestExtensionMixedCase);
+ Assert.equal(type, kTestMimeType);
+
+ // Clean up after ourselves.
+ categoryManager.deleteCategoryEntry(
+ "ext-to-type-mapping",
+ kTestExtension,
+ false
+ );
+}
diff --git a/uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js
new file mode 100644
index 0000000000..5076a57738
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js
@@ -0,0 +1,218 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * 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 for bug 484579 <https://bugzilla.mozilla.org/show_bug.cgi?id=484579>:
+ * nsIMIMEService.getTypeFromExtension may fail unexpectedly on Windows when
+ * "Content Type" is empty in the registry.
+ */
+
+// We must use a file extension that isn't listed in nsExternalHelperAppService's
+// defaultMimeEntries, otherwise the code takes a shortcut skipping the registry.
+const FILE_EXTENSION = ".nfo";
+// This is used to ensure the test properly used the mock, so that if we change
+// the underlying code, it won't be skipped.
+let gTestUsedOurMock = false;
+
+function run_test() {
+ // Activate the override of the file association data in the registry.
+ registerMockWindowsRegKeyFactory();
+
+ // Check the mock has been properly activated.
+ let regKey = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
+ Ci.nsIWindowsRegKey
+ );
+ regKey.open(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT,
+ FILE_EXTENSION,
+ Ci.nsIWindowsRegKey.ACCESS_QUERY_VALUE
+ );
+ Assert.equal(
+ regKey.readStringValue("content type"),
+ "",
+ "Check the mock replied as expected."
+ );
+ Assert.ok(gTestUsedOurMock, "The test properly used the mock registry");
+ // Reset gTestUsedOurMock, because we just used it.
+ gTestUsedOurMock = false;
+ // Try and get the MIME type associated with the extension. If this
+ // operation does not throw an unexpected exception, the test succeeds.
+ Assert.throws(
+ () => {
+ Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromExtension(FILE_EXTENSION);
+ },
+ /NS_ERROR_NOT_AVAILABLE/,
+ "Should throw a NOT_AVAILABLE exception"
+ );
+
+ Assert.ok(gTestUsedOurMock, "The test properly used the mock registry");
+}
+
+/**
+ * Constructs a new mock registry key by wrapping the provided object.
+ *
+ * This mock implementation is tailored for this test, and forces consumers
+ * of the readStringValue method to believe that the "Content Type" value of
+ * the FILE_EXTENSION key under HKEY_CLASSES_ROOT is an empty string.
+ *
+ * The same value read from "HKEY_LOCAL_MACHINE\SOFTWARE\Classes" is not
+ * affected.
+ *
+ * @param aWrappedObject An actual nsIWindowsRegKey implementation.
+ */
+function MockWindowsRegKey(aWrappedObject) {
+ this._wrappedObject = aWrappedObject;
+
+ // This function creates a forwarding function for wrappedObject
+ function makeForwardingFunction(functionName) {
+ return function () {
+ return aWrappedObject[functionName].apply(aWrappedObject, arguments);
+ };
+ }
+
+ // Forward all the functions that are not explicitly overridden
+ for (var propertyName in aWrappedObject) {
+ if (!(propertyName in this)) {
+ if (typeof aWrappedObject[propertyName] == "function") {
+ this[propertyName] = makeForwardingFunction(propertyName);
+ } else {
+ this[propertyName] = aWrappedObject[propertyName];
+ }
+ }
+ }
+}
+
+MockWindowsRegKey.prototype = {
+ // --- Overridden nsISupports interface functions ---
+
+ QueryInterface: ChromeUtils.generateQI(["nsIWindowsRegKey"]),
+
+ // --- Overridden nsIWindowsRegKey interface functions ---
+
+ open(aRootKey, aRelPath, aMode) {
+ // Remember the provided root key and path
+ this._rootKey = aRootKey;
+ this._relPath = aRelPath;
+
+ // Create the actual registry key
+ return this._wrappedObject.open(aRootKey, aRelPath, aMode);
+ },
+
+ openChild(aRelPath, aMode) {
+ // Open the child key and wrap it
+ var innerKey = this._wrappedObject.openChild(aRelPath, aMode);
+ var key = new MockWindowsRegKey(innerKey);
+
+ // Set the properties of the child key and return it
+ key._rootKey = this._rootKey;
+ key._relPath = this._relPath + aRelPath;
+ return key;
+ },
+
+ createChild(aRelPath, aMode) {
+ // Create the child key and wrap it
+ var innerKey = this._wrappedObject.createChild(aRelPath, aMode);
+ var key = new MockWindowsRegKey(innerKey);
+
+ // Set the properties of the child key and return it
+ key._rootKey = this._rootKey;
+ key._relPath = this._relPath + aRelPath;
+ return key;
+ },
+
+ get childCount() {
+ return this._wrappedObject.childCount;
+ },
+
+ get valueCount() {
+ return this._wrappedObject.valueCount;
+ },
+
+ readStringValue(aName) {
+ // If this is the key under test, return a fake value
+ if (
+ this._rootKey == Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT &&
+ this._relPath.toLowerCase() == FILE_EXTENSION &&
+ aName.toLowerCase() == "content type"
+ ) {
+ gTestUsedOurMock = true;
+ return "";
+ }
+ // Return the real value from the registry
+ return this._wrappedObject.readStringValue(aName);
+ },
+};
+
+function registerMockWindowsRegKeyFactory() {
+ const kMockCID = Components.ID("{9b23dfe9-296b-4740-ba1c-d39c9a16e55e}");
+ const kWindowsRegKeyContractID = "@mozilla.org/windows-registry-key;1";
+ // Preserve the original CID.
+ let originalWindowsRegKeyCID = Cc[kWindowsRegKeyContractID].number;
+
+ // See bug 1694345 - nsNotifyAddrListener::CheckAdaptersAddresses might
+ // attempt to use the registry off the main thread, so we disable that
+ // feature while the mock registry is active.
+ let oldSuffixListPref = Services.prefs.getBoolPref(
+ "network.notify.dnsSuffixList"
+ );
+ Services.prefs.setBoolPref("network.notify.dnsSuffixList", false);
+
+ let oldCheckForProxiesPref = Services.prefs.getBoolPref(
+ "network.notify.checkForProxies"
+ );
+ Services.prefs.setBoolPref("network.notify.checkForProxies", false);
+
+ let oldCheckForNRPTPref = Services.prefs.getBoolPref(
+ "network.notify.checkForNRPT"
+ );
+ Services.prefs.setBoolPref("network.notify.checkForNRPT", false);
+
+ info("Create a mock RegKey factory");
+ let originalRegKey = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
+ Ci.nsIWindowsRegKey
+ );
+ let mockWindowsRegKeyFactory = {
+ createInstance(iid) {
+ info("Create a mock wrapper around RegKey");
+ var key = new MockWindowsRegKey(originalRegKey);
+ return key.QueryInterface(iid);
+ },
+ };
+ info("Register the mock RegKey factory");
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(
+ kMockCID,
+ "Mock Windows Registry Key Implementation",
+ kWindowsRegKeyContractID,
+ mockWindowsRegKeyFactory
+ );
+
+ registerCleanupFunction(() => {
+ // Free references to the mock factory
+ registrar.unregisterFactory(kMockCID, mockWindowsRegKeyFactory);
+ // Restore the original factory
+ registrar.registerFactory(
+ Components.ID(originalWindowsRegKeyCID),
+ "",
+ kWindowsRegKeyContractID,
+ null
+ );
+
+ Services.prefs.setBoolPref(
+ "network.notify.dnsSuffixList",
+ oldSuffixListPref
+ );
+ Services.prefs.setBoolPref(
+ "network.notify.checkForProxies",
+ oldCheckForProxiesPref
+ );
+ Services.prefs.setBoolPref(
+ "network.notify.checkForNRPT",
+ oldCheckForNRPTPref
+ );
+ });
+}
diff --git a/uriloader/exthandler/tests/unit/test_handlerService.js b/uriloader/exthandler/tests/unit/test_handlerService.js
new file mode 100644
index 0000000000..46a4d9fdd9
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_handlerService.js
@@ -0,0 +1,467 @@
+/* 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/. */
+
+function run_test() {
+ //* *************************************************************************//
+ // Constants
+
+ const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
+ Ci.nsIHandlerService
+ );
+
+ const mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+
+ const protoSvc = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+
+ const prefSvc = Services.prefs;
+
+ let noMailto = false;
+ if (mozinfo.os == "win") {
+ // Check mailto handler from registry.
+ // If registry entry is nothing, no mailto handler
+ let regSvc = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
+ Ci.nsIWindowsRegKey
+ );
+ try {
+ regSvc.open(regSvc.ROOT_KEY_CLASSES_ROOT, "mailto", regSvc.ACCESS_READ);
+ noMailto = false;
+ } catch (ex) {
+ noMailto = true;
+ }
+ regSvc.close();
+ }
+
+ if (mozinfo.os == "linux") {
+ // Check mailto handler from GIO
+ // If there isn't one, then we have no mailto handler
+ let gIOSvc = Cc["@mozilla.org/gio-service;1"].createInstance(
+ Ci.nsIGIOService
+ );
+ try {
+ gIOSvc.getAppForURIScheme("mailto");
+ noMailto = false;
+ } catch (ex) {
+ noMailto = true;
+ }
+ }
+
+ //* *************************************************************************//
+ // Sample Data
+
+ // It doesn't matter whether or not this nsIFile is actually executable,
+ // only that it has a path and exists. Since we don't know any executable
+ // that exists on all platforms (except possibly the application being
+ // tested, but there doesn't seem to be a way to get a reference to that
+ // from the directory service), we use the temporary directory itself.
+ var executable = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ // XXX We could, of course, create an actual executable in the directory:
+ // executable.append("localhandler");
+ // if (!executable.exists())
+ // executable.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o755);
+
+ var localHandler = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+ localHandler.name = "Local Handler";
+ localHandler.executable = executable;
+
+ var webHandler = Cc[
+ "@mozilla.org/uriloader/web-handler-app;1"
+ ].createInstance(Ci.nsIWebHandlerApp);
+ webHandler.name = "Web Handler";
+ webHandler.uriTemplate = "http://www.example.com/?%s";
+
+ // FIXME: these tests create and manipulate enough variables that it would
+ // make sense to move each test into its own scope so we don't run the risk
+ // of one test stomping on another's data.
+
+ //* *************************************************************************//
+ // Test Default Properties
+
+ // Get a handler info for a MIME type that neither the application nor
+ // the OS knows about and make sure its properties are set to the proper
+ // default values.
+
+ var handlerInfo = mimeSvc.getFromTypeAndExtension("nonexistent/type", null);
+
+ // Make sure it's also an nsIHandlerInfo.
+ Assert.ok(handlerInfo instanceof Ci.nsIHandlerInfo);
+
+ Assert.equal(handlerInfo.type, "nonexistent/type");
+
+ // Deprecated property, but we should still make sure it's set correctly.
+ Assert.equal(handlerInfo.MIMEType, "nonexistent/type");
+
+ // These properties are the ones the handler service knows how to store.
+ Assert.equal(handlerInfo.preferredAction, Ci.nsIHandlerInfo.saveToDisk);
+ Assert.equal(handlerInfo.preferredApplicationHandler, null);
+ Assert.equal(handlerInfo.possibleApplicationHandlers.length, 0);
+ Assert.equal(
+ handlerInfo.alwaysAskBeforeHandling,
+ prefSvc.getBoolPref(
+ "browser.download.always_ask_before_handling_new_types",
+ false
+ )
+ );
+
+ // These properties are initialized to default values by the service,
+ // so we might as well make sure they're initialized to the right defaults.
+ Assert.equal(handlerInfo.description, "");
+ Assert.equal(handlerInfo.hasDefaultHandler, false);
+ Assert.equal(handlerInfo.defaultDescription, "");
+
+ const kExternalWarningDefault =
+ "network.protocol-handler.warn-external-default";
+ prefSvc.setBoolPref(kExternalWarningDefault, true);
+
+ // XXX add more thorough protocol info property checking
+
+ // no OS default handler exists
+ var protoInfo = protoSvc.getProtocolHandlerInfo("x-moz-rheet");
+ Assert.equal(protoInfo.preferredAction, protoInfo.alwaysAsk);
+ Assert.ok(protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default does not exist,
+ // explicit warning pref: false
+ const kExternalWarningPrefPrefix = "network.protocol-handler.warn-external.";
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "http", false);
+ protoInfo = protoSvc.getProtocolHandlerInfo("http");
+ Assert.equal(0, protoInfo.possibleApplicationHandlers.length);
+ // NOTE: this assertion will fail if the system executing the test does not
+ // have a handler registered for the http protocol. This is very unlikely to
+ // actually happen except on certain configurations of Linux, but one of
+ // those configurations is the default WSL Ubuntu install. So, if you are
+ // running this test locally and seeing a failure here, it might not be
+ // anything to really worry about.
+ Assert.ok(!protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default does not exist,
+ // explicit warning pref: true
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "http", true);
+ protoInfo = protoSvc.getProtocolHandlerInfo("http");
+ // OS handler isn't included in possibleApplicationHandlers, so length is 0
+ // Once they become instances of nsILocalHandlerApp, this number will need
+ // to change.
+ Assert.equal(0, protoInfo.possibleApplicationHandlers.length);
+ Assert.ok(protoInfo.alwaysAskBeforeHandling);
+
+ // OS default exists, injected default exists, explicit warning pref: false
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "mailto", false);
+ protoInfo = protoSvc.getProtocolHandlerInfo("mailto");
+ if (AppConstants.MOZ_APP_NAME == "thunderbird") {
+ Assert.equal(0, protoInfo.possibleApplicationHandlers.length);
+ } else {
+ Assert.equal(1, protoInfo.possibleApplicationHandlers.length);
+ }
+
+ // Win7+ or Linux's GIO might not have a default mailto: handler
+ if (noMailto) {
+ Assert.ok(protoInfo.alwaysAskBeforeHandling);
+ } else {
+ Assert.ok(!protoInfo.alwaysAskBeforeHandling);
+ }
+
+ // OS default exists, injected default exists, explicit warning pref: true
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "mailto", true);
+ protoInfo = protoSvc.getProtocolHandlerInfo("mailto");
+ if (AppConstants.MOZ_APP_NAME == "thunderbird") {
+ Assert.equal(0, protoInfo.possibleApplicationHandlers.length);
+ } else {
+ Assert.equal(1, protoInfo.possibleApplicationHandlers.length);
+ // Win7+ or Linux's GIO may have no default mailto: handler, so we'd ask
+ // anyway. Otherwise, the default handlers will not have stored preferred
+ // actions etc., so re-requesting them after the warning pref has changed
+ // will use the updated pref value. So both when we have and do not have
+ // a default mailto: handler, we'll ask:
+ Assert.ok(protoInfo.alwaysAskBeforeHandling);
+ // As soon as anyone actually stores updated defaults into the profile
+ // database, that default will stop tracking the warning pref.
+ }
+ // Now set the value stored in RDF to true, and the pref to false, to make
+ // sure we still get the right value. (Basically, same thing as above but
+ // with the values reversed.)
+ prefSvc.setBoolPref(kExternalWarningPrefPrefix + "mailto", false);
+ protoInfo.alwaysAskBeforeHandling = true;
+ handlerSvc.store(protoInfo);
+ protoInfo = protoSvc.getProtocolHandlerInfo("mailto");
+ if (AppConstants.MOZ_APP_NAME == "thunderbird") {
+ Assert.equal(0, protoInfo.possibleApplicationHandlers.length);
+ } else {
+ Assert.equal(1, protoInfo.possibleApplicationHandlers.length);
+ Assert.ok(protoInfo.alwaysAskBeforeHandling);
+ }
+
+ //* *************************************************************************//
+ // Test Round-Trip Data Integrity
+
+ // Test round-trip data integrity by setting the properties of the handler
+ // info object to different values, telling the handler service to store the
+ // object, and then retrieving a new info object for the same type and making
+ // sure its properties are identical.
+
+ handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ handlerInfo.preferredApplicationHandler = localHandler;
+ handlerInfo.alwaysAskBeforeHandling = false;
+
+ handlerSvc.store(handlerInfo);
+
+ handlerInfo = mimeSvc.getFromTypeAndExtension("nonexistent/type", null);
+
+ Assert.equal(handlerInfo.preferredAction, Ci.nsIHandlerInfo.useHelperApp);
+
+ Assert.notEqual(handlerInfo.preferredApplicationHandler, null);
+ var preferredHandler = handlerInfo.preferredApplicationHandler;
+ Assert.equal(typeof preferredHandler, "object");
+ Assert.equal(preferredHandler.name, "Local Handler");
+ Assert.ok(preferredHandler instanceof Ci.nsILocalHandlerApp);
+ preferredHandler.QueryInterface(Ci.nsILocalHandlerApp);
+ Assert.equal(preferredHandler.executable.path, localHandler.executable.path);
+
+ Assert.ok(!handlerInfo.alwaysAskBeforeHandling);
+
+ // Make sure the handler service's enumerate method lists all known handlers.
+ var handlerInfo2 = mimeSvc.getFromTypeAndExtension("nonexistent/type2", null);
+ handlerSvc.store(handlerInfo2);
+ var handlerTypes = ["nonexistent/type", "nonexistent/type2"];
+ handlerTypes.push("mailto");
+ for (let handler of handlerSvc.enumerate()) {
+ Assert.notEqual(handlerTypes.indexOf(handler.type), -1);
+ handlerTypes.splice(handlerTypes.indexOf(handler.type), 1);
+ }
+ Assert.equal(handlerTypes.length, 0);
+ // Make sure the handler service's remove method removes a handler record.
+ handlerSvc.remove(handlerInfo2);
+ let handlers = handlerSvc.enumerate();
+ while (handlers.hasMoreElements()) {
+ Assert.notEqual(
+ handlers.getNext().QueryInterface(Ci.nsIHandlerInfo).type,
+ handlerInfo2.type
+ );
+ }
+
+ // Make sure we can store and retrieve a handler info object with no preferred
+ // handler.
+ var noPreferredHandlerInfo = mimeSvc.getFromTypeAndExtension(
+ "nonexistent/no-preferred-handler",
+ null
+ );
+ handlerSvc.store(noPreferredHandlerInfo);
+ noPreferredHandlerInfo = mimeSvc.getFromTypeAndExtension(
+ "nonexistent/no-preferred-handler",
+ null
+ );
+ Assert.equal(noPreferredHandlerInfo.preferredApplicationHandler, null);
+
+ // Make sure that the handler service removes an existing handler record
+ // if we store a handler info object with no preferred handler.
+ var removePreferredHandlerInfo = mimeSvc.getFromTypeAndExtension(
+ "nonexistent/rem-preferred-handler",
+ null
+ );
+ removePreferredHandlerInfo.preferredApplicationHandler = localHandler;
+ handlerSvc.store(removePreferredHandlerInfo);
+ removePreferredHandlerInfo = mimeSvc.getFromTypeAndExtension(
+ "nonexistent/rem-preferred-handler",
+ null
+ );
+ removePreferredHandlerInfo.preferredApplicationHandler = null;
+ handlerSvc.store(removePreferredHandlerInfo);
+ removePreferredHandlerInfo = mimeSvc.getFromTypeAndExtension(
+ "nonexistent/rem-preferred-handler",
+ null
+ );
+ Assert.equal(removePreferredHandlerInfo.preferredApplicationHandler, null);
+
+ // Make sure we can store and retrieve a handler info object with possible
+ // handlers. We test both adding and removing handlers.
+
+ // Get a handler info and make sure it has no possible handlers.
+ var possibleHandlersInfo = mimeSvc.getFromTypeAndExtension(
+ "nonexistent/possible-handlers",
+ null
+ );
+ Assert.equal(possibleHandlersInfo.possibleApplicationHandlers.length, 0);
+
+ // Store and re-retrieve the handler and make sure it still has no possible
+ // handlers.
+ handlerSvc.store(possibleHandlersInfo);
+ possibleHandlersInfo = mimeSvc.getFromTypeAndExtension(
+ "nonexistent/possible-handlers",
+ null
+ );
+ Assert.equal(possibleHandlersInfo.possibleApplicationHandlers.length, 0);
+
+ // Add two handlers, store the object, re-retrieve it, and make sure it has
+ // two handlers.
+ possibleHandlersInfo.possibleApplicationHandlers.appendElement(localHandler);
+ possibleHandlersInfo.possibleApplicationHandlers.appendElement(webHandler);
+ handlerSvc.store(possibleHandlersInfo);
+ possibleHandlersInfo = mimeSvc.getFromTypeAndExtension(
+ "nonexistent/possible-handlers",
+ null
+ );
+ Assert.equal(possibleHandlersInfo.possibleApplicationHandlers.length, 2);
+
+ // Figure out which is the local and which is the web handler and the index
+ // in the array of the local handler, which is the one we're going to remove
+ // to test removal of a handler.
+ var handler1 =
+ possibleHandlersInfo.possibleApplicationHandlers.queryElementAt(
+ 0,
+ Ci.nsIHandlerApp
+ );
+ var handler2 =
+ possibleHandlersInfo.possibleApplicationHandlers.queryElementAt(
+ 1,
+ Ci.nsIHandlerApp
+ );
+ var localPossibleHandler, webPossibleHandler, localIndex;
+ if (handler1 instanceof Ci.nsILocalHandlerApp) {
+ [localPossibleHandler, webPossibleHandler, localIndex] = [
+ handler1,
+ handler2,
+ 0,
+ ];
+ } else {
+ [localPossibleHandler, webPossibleHandler, localIndex] = [
+ handler2,
+ handler1,
+ 1,
+ ];
+ }
+ localPossibleHandler.QueryInterface(Ci.nsILocalHandlerApp);
+ webPossibleHandler.QueryInterface(Ci.nsIWebHandlerApp);
+
+ // Make sure the two handlers are the ones we stored.
+ Assert.equal(localPossibleHandler.name, localHandler.name);
+ Assert.ok(localPossibleHandler.equals(localHandler));
+ Assert.equal(webPossibleHandler.name, webHandler.name);
+ Assert.ok(webPossibleHandler.equals(webHandler));
+
+ // Remove a handler, store the object, re-retrieve it, and make sure
+ // it only has one handler.
+ possibleHandlersInfo.possibleApplicationHandlers.removeElementAt(localIndex);
+ handlerSvc.store(possibleHandlersInfo);
+ possibleHandlersInfo = mimeSvc.getFromTypeAndExtension(
+ "nonexistent/possible-handlers",
+ null
+ );
+ Assert.equal(possibleHandlersInfo.possibleApplicationHandlers.length, 1);
+
+ // Make sure the handler is the one we didn't remove.
+ webPossibleHandler =
+ possibleHandlersInfo.possibleApplicationHandlers.queryElementAt(
+ 0,
+ Ci.nsIWebHandlerApp
+ );
+ Assert.equal(webPossibleHandler.name, webHandler.name);
+ Assert.ok(webPossibleHandler.equals(webHandler));
+
+ // ////////////////////////////////////////////////////
+ // handler info command line parameters and equality
+ var localApp = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+ var handlerApp = localApp.QueryInterface(Ci.nsIHandlerApp);
+
+ Assert.ok(handlerApp.equals(localApp));
+
+ localApp.executable = executable;
+
+ Assert.equal(0, localApp.parameterCount);
+ localApp.appendParameter("-test1");
+ Assert.equal(1, localApp.parameterCount);
+ localApp.appendParameter("-test2");
+ Assert.equal(2, localApp.parameterCount);
+ Assert.ok(localApp.parameterExists("-test1"));
+ Assert.ok(localApp.parameterExists("-test2"));
+ Assert.ok(!localApp.parameterExists("-false"));
+ localApp.clearParameters();
+ Assert.equal(0, localApp.parameterCount);
+
+ var localApp2 = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+
+ localApp2.executable = executable;
+
+ localApp.clearParameters();
+ Assert.ok(localApp.equals(localApp2));
+
+ // equal:
+ // cut -d 1 -f 2
+ // cut -d 1 -f 2
+
+ localApp.appendParameter("-test1");
+ localApp.appendParameter("-test2");
+ localApp.appendParameter("-test3");
+ localApp2.appendParameter("-test1");
+ localApp2.appendParameter("-test2");
+ localApp2.appendParameter("-test3");
+ Assert.ok(localApp.equals(localApp2));
+
+ // not equal:
+ // cut -d 1 -f 2
+ // cut -f 1 -d 2
+
+ localApp.clearParameters();
+ localApp2.clearParameters();
+
+ localApp.appendParameter("-test1");
+ localApp.appendParameter("-test2");
+ localApp.appendParameter("-test3");
+ localApp2.appendParameter("-test2");
+ localApp2.appendParameter("-test1");
+ localApp2.appendParameter("-test3");
+ Assert.ok(!localApp2.equals(localApp));
+
+ var str;
+ str = localApp.getParameter(0);
+ Assert.equal(str, "-test1");
+ str = localApp.getParameter(1);
+ Assert.equal(str, "-test2");
+ str = localApp.getParameter(2);
+ Assert.equal(str, "-test3");
+
+ // FIXME: test round trip integrity for a protocol.
+ // FIXME: test round trip integrity for a handler info with a web handler.
+
+ //* *************************************************************************//
+ // getTypeFromExtension tests
+
+ // test nonexistent extension
+ var lolType = handlerSvc.getTypeFromExtension("lolcat");
+ Assert.equal(lolType, "");
+
+ // add a handler for the extension
+ var lolHandler = mimeSvc.getFromTypeAndExtension("application/lolcat", null);
+
+ Assert.ok(!lolHandler.extensionExists("lolcat"));
+ lolHandler.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+ lolHandler.preferredApplicationHandler = localHandler;
+ lolHandler.alwaysAskBeforeHandling = false;
+ lolHandler.appendExtension("lolcat");
+
+ // store the handler
+ Assert.ok(!handlerSvc.exists(lolHandler));
+ handlerSvc.store(lolHandler);
+ Assert.ok(handlerSvc.exists(lolHandler));
+
+ // test now-existent extension
+ lolType = handlerSvc.getTypeFromExtension("lolcat");
+ Assert.equal(lolType, "application/lolcat");
+
+ // test mailcap entries with needsterminal are ignored on non-Windows non-Mac.
+ if (mozinfo.os != "win" && mozinfo.os != "mac") {
+ prefSvc.setStringPref(
+ "helpers.private_mailcap_file",
+ do_get_file("mailcap").path
+ );
+ handlerInfo = mimeSvc.getFromTypeAndExtension("text/plain", null);
+ Assert.equal(handlerInfo.preferredAction, Ci.nsIHandlerInfo.saveToDisk);
+ Assert.equal(handlerInfo.defaultDescription, "sed");
+ }
+}
diff --git a/uriloader/exthandler/tests/unit/test_handlerService_store.js b/uriloader/exthandler/tests/unit/test_handlerService_store.js
new file mode 100644
index 0000000000..03573dbc2c
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_handlerService_store.js
@@ -0,0 +1,752 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests the nsIHandlerService interface.
+ */
+
+// Set up an nsIWebHandlerApp instance that can be used in multiple tests.
+let webHandlerApp = Cc[
+ "@mozilla.org/uriloader/web-handler-app;1"
+].createInstance(Ci.nsIWebHandlerApp);
+webHandlerApp.name = "Web Handler";
+webHandlerApp.uriTemplate = "https://www.example.com/?url=%s";
+let expectedWebHandlerApp = {
+ name: webHandlerApp.name,
+ uriTemplate: webHandlerApp.uriTemplate,
+};
+
+// Set up an nsILocalHandlerApp instance that can be used in multiple tests. The
+// executable should exist, but it doesn't need to point to an actual file, so
+// we simply initialize it to the path of an existing directory.
+let localHandlerApp = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+].createInstance(Ci.nsILocalHandlerApp);
+localHandlerApp.name = "Local Handler";
+localHandlerApp.executable = FileUtils.getFile("TmpD", []);
+let expectedLocalHandlerApp = {
+ name: localHandlerApp.name,
+ executable: localHandlerApp.executable,
+};
+
+/**
+ * Returns a new nsIHandlerInfo instance initialized to known values that don't
+ * depend on the platform and are easier to verify later.
+ *
+ * @param type
+ * Because the "preferredAction" is initialized to saveToDisk, this
+ * should represent a MIME type rather than a protocol.
+ */
+function getKnownHandlerInfo(type) {
+ let handlerInfo = HandlerServiceTestUtils.getBlankHandlerInfo(type);
+ handlerInfo.preferredAction = Ci.nsIHandlerInfo.saveToDisk;
+ handlerInfo.alwaysAskBeforeHandling = false;
+ return handlerInfo;
+}
+
+/**
+ * Checks that the information stored in the handler service instance under
+ * testing matches the test data files.
+ */
+function assertAllHandlerInfosMatchTestData() {
+ let handlerInfos = HandlerServiceTestUtils.getAllHandlerInfos();
+
+ // It's important that the MIME types we check here do not exist at the
+ // operating system level, otherwise the list of handlers and file extensions
+ // will be merged. The current implementation avoids duplicate entries.
+
+ HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
+ type: "example/type.handleinternally",
+ preferredAction: Ci.nsIHandlerInfo.handleInternally,
+ alwaysAskBeforeHandling: false,
+ fileExtensions: ["example_one"],
+ });
+
+ HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
+ type: "example/type.savetodisk",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: true,
+ preferredApplicationHandler: {
+ name: "Example Default Handler",
+ uriTemplate: "https://www.example.com/?url=%s",
+ },
+ possibleApplicationHandlers: [
+ {
+ name: "Example Default Handler",
+ uriTemplate: "https://www.example.com/?url=%s",
+ },
+ ],
+ fileExtensions: ["example_two", "example_three"],
+ });
+
+ HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
+ type: "example/type.usehelperapp",
+ preferredAction: Ci.nsIHandlerInfo.useHelperApp,
+ alwaysAskBeforeHandling: true,
+ preferredApplicationHandler: {
+ name: "Example Default Handler",
+ uriTemplate: "https://www.example.com/?url=%s",
+ },
+ possibleApplicationHandlers: [
+ {
+ name: "Example Default Handler",
+ uriTemplate: "https://www.example.com/?url=%s",
+ },
+ {
+ name: "Example Possible Handler One",
+ uriTemplate: "http://www.example.com/?id=1&url=%s",
+ },
+ {
+ name: "Example Possible Handler Two",
+ uriTemplate: "http://www.example.com/?id=2&url=%s",
+ },
+ ],
+ fileExtensions: ["example_two", "example_three"],
+ });
+
+ HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
+ type: "example/type.usesystemdefault",
+ preferredAction: Ci.nsIHandlerInfo.useSystemDefault,
+ alwaysAskBeforeHandling: false,
+ possibleApplicationHandlers: [
+ {
+ name: "Example Possible Handler",
+ uriTemplate: "http://www.example.com/?url=%s",
+ },
+ ],
+ });
+
+ HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
+ type: "examplescheme.usehelperapp",
+ preferredAction: Ci.nsIHandlerInfo.useHelperApp,
+ alwaysAskBeforeHandling: true,
+ preferredApplicationHandler: {
+ name: "Example Default Handler",
+ uriTemplate: "https://www.example.com/?url=%s",
+ },
+ possibleApplicationHandlers: [
+ {
+ name: "Example Default Handler",
+ uriTemplate: "https://www.example.com/?url=%s",
+ },
+ {
+ name: "Example Possible Handler One",
+ uriTemplate: "http://www.example.com/?id=1&url=%s",
+ },
+ {
+ name: "Example Possible Handler Two",
+ uriTemplate: "http://www.example.com/?id=2&url=%s",
+ },
+ ],
+ });
+
+ HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
+ type: "examplescheme.usesystemdefault",
+ preferredAction: Ci.nsIHandlerInfo.useSystemDefault,
+ alwaysAskBeforeHandling: false,
+ possibleApplicationHandlers: [
+ {
+ name: "Example Possible Handler",
+ uriTemplate: "http://www.example.com/?url=%s",
+ },
+ ],
+ });
+
+ Assert.equal(handlerInfos.length, 0);
+}
+
+/**
+ * Loads data from a file in a predefined format, verifying that the format is
+ * recognized and all the known properties are loaded and saved.
+ */
+add_task(async function test_store_fillHandlerInfo_predefined() {
+ // Test that the file format used in previous versions can be loaded.
+ await copyTestDataToHandlerStore();
+ await assertAllHandlerInfosMatchTestData();
+
+ // Keep a copy of the nsIHandlerInfo instances, then delete the handler store
+ // and populate it with the known data. Since the handler store is empty, the
+ // default handlers for the current locale are also injected, so we have to
+ // delete them manually before adding the other nsIHandlerInfo instances.
+ let testHandlerInfos = HandlerServiceTestUtils.getAllHandlerInfos();
+ await deleteHandlerStore();
+ for (let handlerInfo of HandlerServiceTestUtils.getAllHandlerInfos()) {
+ gHandlerService.remove(handlerInfo);
+ }
+ for (let handlerInfo of testHandlerInfos) {
+ gHandlerService.store(handlerInfo);
+ }
+
+ // Test that the known data still matches after saving it and reloading.
+ await unloadHandlerStore();
+ await assertAllHandlerInfosMatchTestData();
+});
+
+/**
+ * Check that "store" is able to add new instances, that "remove" and "exists"
+ * work, and that "fillHandlerInfo" throws when the instance does not exist.
+ */
+add_task(async function test_store_remove_exists() {
+ // Test both MIME types and protocols.
+ for (let type of [
+ "example/type.usehelperapp",
+ "examplescheme.usehelperapp",
+ ]) {
+ // Create new nsIHandlerInfo instances before loading the test data.
+ await deleteHandlerStore();
+ let handlerInfoPresent = HandlerServiceTestUtils.getHandlerInfo(type);
+ let handlerInfoAbsent = HandlerServiceTestUtils.getHandlerInfo(type + "2");
+
+ // Set up known properties that we can verify later.
+ handlerInfoAbsent.preferredAction = Ci.nsIHandlerInfo.saveToDisk;
+ handlerInfoAbsent.alwaysAskBeforeHandling = false;
+
+ await copyTestDataToHandlerStore();
+
+ Assert.ok(gHandlerService.exists(handlerInfoPresent));
+ Assert.ok(!gHandlerService.exists(handlerInfoAbsent));
+
+ gHandlerService.store(handlerInfoAbsent);
+ gHandlerService.remove(handlerInfoPresent);
+
+ await unloadHandlerStore();
+
+ Assert.ok(!gHandlerService.exists(handlerInfoPresent));
+ Assert.ok(gHandlerService.exists(handlerInfoAbsent));
+
+ Assert.throws(
+ () => gHandlerService.fillHandlerInfo(handlerInfoPresent, ""),
+ ex => ex.result == Cr.NS_ERROR_NOT_AVAILABLE
+ );
+
+ let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo(type + "2");
+ HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+ type: type + "2",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: false,
+ });
+ }
+});
+
+/**
+ * Tests that it is possible to save an nsIHandlerInfo instance with a
+ * "preferredAction" that is either a valid or an unknown value, and the
+ * action always takes on an appropriate value when reloading.
+ */
+add_task(async function test_store_preferredAction() {
+ await deleteHandlerStore();
+
+ let handlerInfo = getKnownHandlerInfo("example/new");
+ // Valid action values should all remain unchanged across a refresh, except
+ // for alwaysAsk which may be overridden with useHelperApp depending on prefs.
+ // Invalid action values should always convert to useHelperApp.
+ const actions = [
+ {
+ preferred: Ci.nsIHandlerInfo.alwaysAsk,
+ expected: Ci.nsIHandlerInfo.alwaysAsk,
+ },
+ {
+ preferred: Ci.nsIHandlerInfo.handleInternally,
+ expected: Ci.nsIHandlerInfo.handleInternally,
+ },
+ { preferred: 999, expected: Ci.nsIHandlerInfo.useHelperApp },
+ ];
+
+ for (let action of actions) {
+ handlerInfo.preferredAction = action.preferred;
+ gHandlerService.store(handlerInfo);
+ gHandlerService.fillHandlerInfo(handlerInfo, "");
+ Assert.equal(handlerInfo.preferredAction, action.expected);
+ }
+});
+
+/**
+ * Tests that it is possible to save an nsIHandlerInfo instance containing an
+ * nsILocalHandlerApp instance pointing to an executable that doesn't exist, but
+ * this entry is ignored when reloading.
+ */
+add_task(async function test_store_localHandlerApp_missing() {
+ if (!("@mozilla.org/uriloader/dbus-handler-app;1" in Cc)) {
+ info("Skipping test because it does not apply to this platform.");
+ return;
+ }
+
+ let missingHandlerApp = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+ missingHandlerApp.name = "Non-existing Handler";
+ missingHandlerApp.executable = FileUtils.getFile("TmpD", ["nonexisting"]);
+
+ await deleteHandlerStore();
+
+ let handlerInfo = getKnownHandlerInfo("example/new");
+ handlerInfo.preferredApplicationHandler = missingHandlerApp;
+ handlerInfo.possibleApplicationHandlers.appendElement(missingHandlerApp);
+ handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
+ gHandlerService.store(handlerInfo);
+
+ await unloadHandlerStore();
+
+ let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
+ HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+ type: "example/new",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: false,
+ possibleApplicationHandlers: [expectedWebHandlerApp],
+ });
+});
+
+/**
+ * Test saving and reloading an instance of nsIDBusHandlerApp.
+ */
+add_task(async function test_store_dBusHandlerApp() {
+ if (!("@mozilla.org/uriloader/dbus-handler-app;1" in Cc)) {
+ info("Skipping test because it does not apply to this platform.");
+ return;
+ }
+
+ // Set up an nsIDBusHandlerApp instance for testing.
+ let dBusHandlerApp = Cc[
+ "@mozilla.org/uriloader/dbus-handler-app;1"
+ ].createInstance(Ci.nsIDBusHandlerApp);
+ dBusHandlerApp.name = "DBus Handler";
+ dBusHandlerApp.service = "test.method.server";
+ dBusHandlerApp.method = "Method";
+ dBusHandlerApp.dBusInterface = "test.method.Type";
+ dBusHandlerApp.objectPath = "/test/method/Object";
+ let expectedDBusHandlerApp = {
+ name: dBusHandlerApp.name,
+ service: dBusHandlerApp.service,
+ method: dBusHandlerApp.method,
+ dBusInterface: dBusHandlerApp.dBusInterface,
+ objectPath: dBusHandlerApp.objectPath,
+ };
+
+ await deleteHandlerStore();
+
+ let handlerInfo = getKnownHandlerInfo("example/new");
+ handlerInfo.preferredApplicationHandler = dBusHandlerApp;
+ handlerInfo.possibleApplicationHandlers.appendElement(dBusHandlerApp);
+ gHandlerService.store(handlerInfo);
+
+ await unloadHandlerStore();
+
+ let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
+ HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+ type: "example/new",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: false,
+ preferredApplicationHandler: expectedDBusHandlerApp,
+ possibleApplicationHandlers: [expectedDBusHandlerApp],
+ });
+});
+
+/**
+ * Tests that it is possible to save an nsIHandlerInfo instance with a
+ * "preferredApplicationHandler" and no "possibleApplicationHandlers", but the
+ * former is always included in the latter list when reloading.
+ */
+add_task(
+ async function test_store_possibleApplicationHandlers_includes_preferred() {
+ await deleteHandlerStore();
+
+ let handlerInfo = getKnownHandlerInfo("example/new");
+ handlerInfo.preferredApplicationHandler = localHandlerApp;
+ gHandlerService.store(handlerInfo);
+
+ await unloadHandlerStore();
+
+ let actualHandlerInfo =
+ HandlerServiceTestUtils.getHandlerInfo("example/new");
+ HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+ type: "example/new",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: false,
+ preferredApplicationHandler: expectedLocalHandlerApp,
+ possibleApplicationHandlers: [expectedLocalHandlerApp],
+ });
+ }
+);
+
+/**
+ * Tests that it is possible to save an nsIHandlerInfo instance with a
+ * "preferredApplicationHandler" that is not the first element in
+ * "possibleApplicationHandlers", but the former is always included as the first
+ * element of the latter list when reloading.
+ */
+add_task(
+ async function test_store_possibleApplicationHandlers_preferred_first() {
+ await deleteHandlerStore();
+
+ let handlerInfo = getKnownHandlerInfo("example/new");
+ handlerInfo.preferredApplicationHandler = webHandlerApp;
+ // The preferred handler is appended after the other one.
+ handlerInfo.possibleApplicationHandlers.appendElement(localHandlerApp);
+ handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
+ gHandlerService.store(handlerInfo);
+
+ await unloadHandlerStore();
+
+ let actualHandlerInfo =
+ HandlerServiceTestUtils.getHandlerInfo("example/new");
+ HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+ type: "example/new",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: false,
+ preferredApplicationHandler: expectedWebHandlerApp,
+ possibleApplicationHandlers: [
+ expectedWebHandlerApp,
+ expectedLocalHandlerApp,
+ ],
+ });
+ }
+);
+
+/**
+ * Tests that it is possible to save an nsIHandlerInfo instance with an
+ * uppercase file extension, but it is converted to lowercase when reloading.
+ */
+add_task(async function test_store_fileExtensions_lowercase() {
+ await deleteHandlerStore();
+
+ let handlerInfo = getKnownHandlerInfo("example/new");
+ handlerInfo.appendExtension("extension_test1");
+ handlerInfo.appendExtension("EXTENSION_test2");
+ gHandlerService.store(handlerInfo);
+
+ await unloadHandlerStore();
+
+ let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
+ HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+ type: "example/new",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: false,
+ fileExtensions: ["extension_test1", "extension_test2"],
+ });
+});
+
+/**
+ * Tests that appendExtension doesn't add duplicates, and that anyway duplicates
+ * from possibleApplicationHandlers are removed when saving and reloading.
+ */
+add_task(async function test_store_no_duplicates() {
+ await deleteHandlerStore();
+
+ let handlerInfo = getKnownHandlerInfo("example/new");
+ handlerInfo.preferredApplicationHandler = webHandlerApp;
+ handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
+ handlerInfo.possibleApplicationHandlers.appendElement(localHandlerApp);
+ handlerInfo.possibleApplicationHandlers.appendElement(localHandlerApp);
+ handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
+ handlerInfo.appendExtension("extension_test1");
+ handlerInfo.appendExtension("extension_test2");
+ handlerInfo.appendExtension("extension_test1");
+ handlerInfo.appendExtension("EXTENSION_test1");
+ Assert.deepEqual(Array.from(handlerInfo.getFileExtensions()), [
+ "extension_test1",
+ "extension_test2",
+ ]);
+ gHandlerService.store(handlerInfo);
+
+ await unloadHandlerStore();
+
+ let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
+ HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+ type: "example/new",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: false,
+ preferredApplicationHandler: expectedWebHandlerApp,
+ possibleApplicationHandlers: [
+ expectedWebHandlerApp,
+ expectedLocalHandlerApp,
+ ],
+ fileExtensions: ["extension_test1", "extension_test2"],
+ });
+});
+
+/**
+ * Tests that setFileExtensions doesn't add duplicates.
+ */
+add_task(async function test_setFileExtensions_no_duplicates() {
+ await deleteHandlerStore();
+
+ let handlerInfo = getKnownHandlerInfo("example/new");
+ handlerInfo.setFileExtensions("a,b,A,b,c,a");
+ let expected = ["a", "b", "c"];
+ Assert.deepEqual(Array.from(handlerInfo.getFileExtensions()), expected);
+ // Test empty extensions, also at begin and end.
+ handlerInfo.setFileExtensions(",a,,b,A,c,");
+ Assert.deepEqual(Array.from(handlerInfo.getFileExtensions()), expected);
+});
+
+/**
+ * Tests that "store" deletes properties that have their default values from
+ * the data store.
+ *
+ * File extensions are never deleted once they have been associated.
+ */
+add_task(async function test_store_deletes_properties_except_extensions() {
+ await deleteHandlerStore();
+
+ // Prepare an nsIHandlerInfo instance with all the properties set to values
+ // that will result in deletions. The preferredAction is also set to a defined
+ // value so we can more easily verify it later.
+ let handlerInfo = HandlerServiceTestUtils.getBlankHandlerInfo(
+ "example/type.savetodisk"
+ );
+ handlerInfo.preferredAction = Ci.nsIHandlerInfo.saveToDisk;
+ handlerInfo.alwaysAskBeforeHandling = false;
+
+ // All the properties for "example/type.savetodisk" are present in the test
+ // data, so we load the data before overwriting their values.
+ await copyTestDataToHandlerStore();
+ gHandlerService.store(handlerInfo);
+
+ // Now we can reload the data and verify that no extra values have been kept.
+ await unloadHandlerStore();
+ let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo(
+ "example/type.savetodisk"
+ );
+ HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+ type: "example/type.savetodisk",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: false,
+ fileExtensions: ["example_two", "example_three"],
+ });
+});
+
+/**
+ * Tests the "overrideType" argument of "fillHandlerInfo".
+ */
+add_task(async function test_fillHandlerInfo_overrideType() {
+ // Test both MIME types and protocols.
+ for (let type of [
+ "example/type.usesystemdefault",
+ "examplescheme.usesystemdefault",
+ ]) {
+ await deleteHandlerStore();
+
+ // Create new nsIHandlerInfo instances before loading the test data.
+ let handlerInfoAbsent = HandlerServiceTestUtils.getHandlerInfo(type + "2");
+
+ // Fill the nsIHandlerInfo instance using the type that actually exists.
+ await copyTestDataToHandlerStore();
+ gHandlerService.fillHandlerInfo(handlerInfoAbsent, type);
+ HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfoAbsent, {
+ // While the data is populated from another type, the type is unchanged.
+ type: type + "2",
+ preferredAction: Ci.nsIHandlerInfo.useSystemDefault,
+ alwaysAskBeforeHandling: false,
+ possibleApplicationHandlers: [
+ {
+ name: "Example Possible Handler",
+ uriTemplate: "http://www.example.com/?url=%s",
+ },
+ ],
+ });
+ }
+});
+
+/**
+ * Tests "getTypeFromExtension" including unknown extensions.
+ */
+add_task(async function test_getTypeFromExtension() {
+ await copyTestDataToHandlerStore();
+
+ Assert.equal(gHandlerService.getTypeFromExtension(""), "");
+ Assert.equal(gHandlerService.getTypeFromExtension("example_unknown"), "");
+ Assert.equal(
+ gHandlerService.getTypeFromExtension("example_one"),
+ "example/type.handleinternally"
+ );
+ Assert.equal(
+ gHandlerService.getTypeFromExtension("EXAMPLE_one"),
+ "example/type.handleinternally"
+ );
+});
+
+/**
+ * Checks that the information stored in the handler service instance under
+ * testing matches the default handlers for the English locale.
+ */
+function assertAllHandlerInfosMatchDefaultHandlers() {
+ let handlerInfos = HandlerServiceTestUtils.getAllHandlerInfos();
+
+ HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), {
+ type: "mailto",
+ preferredActionOSDependent: true,
+ possibleApplicationHandlers: [
+ {
+ name: "Gmail",
+ uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
+ },
+ ],
+ });
+
+ Assert.equal(handlerInfos.length, 0);
+}
+
+/**
+ * Tests the default protocol handlers imported from the locale-specific data.
+ */
+add_task(
+ { skip_if: () => AppConstants.MOZ_APP_NAME == "thunderbird" },
+ async function test_default_protocol_handlers() {
+ if (
+ !Services.prefs.getPrefType("gecko.handlerService.defaultHandlersVersion")
+ ) {
+ info("This platform or locale does not have default handlers.");
+ return;
+ }
+
+ // This will inject the default protocol handlers for the current locale.
+ await deleteHandlerStore();
+
+ await assertAllHandlerInfosMatchDefaultHandlers();
+ }
+);
+
+/**
+ * Tests that the default protocol handlers are not imported again from the
+ * locale-specific data if they already exist.
+ */
+add_task(
+ { skip_if: () => AppConstants.MOZ_APP_NAME == "thunderbird" },
+ async function test_default_protocol_handlers_no_duplicates() {
+ if (
+ !Services.prefs.getPrefType("gecko.handlerService.defaultHandlersVersion")
+ ) {
+ info("This platform or locale does not have default handlers.");
+ return;
+ }
+
+ // This will inject the default protocol handlers for the current locale.
+ await deleteHandlerStore();
+
+ // Clear the preference to force injecting again.
+ Services.prefs.clearUserPref("gecko.handlerService.defaultHandlersVersion");
+
+ await unloadHandlerStore();
+
+ // There should be no duplicate handlers in the protocols.
+ assertAllHandlerInfosMatchDefaultHandlers();
+ }
+);
+
+/**
+ * Ensures forward compatibility by checking that the "store" method preserves
+ * unknown properties in the test data.
+ */
+add_task(async function test_store_keeps_unknown_properties() {
+ // Create a new nsIHandlerInfo instance before loading the test data.
+ await deleteHandlerStore();
+ let handlerInfo = HandlerServiceTestUtils.getHandlerInfo(
+ "example/type.handleinternally"
+ );
+
+ await copyTestDataToHandlerStore();
+ gHandlerService.store(handlerInfo);
+
+ await unloadHandlerStore();
+ let data = await IOUtils.readJSON(jsonPath);
+ Assert.equal(
+ data.mimeTypes["example/type.handleinternally"].unknownProperty,
+ "preserved"
+ );
+});
+
+/**
+ * Runs the asyncInit method, ensuring that it successfully inits the store
+ * and calls the handlersvc-store-initialized topic.
+ */
+add_task(async function test_async_init() {
+ await deleteHandlerStore();
+ await copyTestDataToHandlerStore();
+ gHandlerService.asyncInit();
+ await TestUtils.topicObserved("handlersvc-store-initialized");
+ await assertAllHandlerInfosMatchTestData();
+
+ await unloadHandlerStore();
+});
+
+/**
+ * Races the asyncInit method against the sync init (implicit in enumerate),
+ * to ensure that the store will be synchronously initialized without any
+ * ill effects.
+ */
+add_task(async function test_race_async_init() {
+ await deleteHandlerStore();
+ await copyTestDataToHandlerStore();
+ let storeInitialized = false;
+ // Pass a callback to synchronously observe the topic, as a promise would
+ // resolve asynchronously
+ TestUtils.topicObserved("handlersvc-store-initialized", () => {
+ storeInitialized = true;
+ return true;
+ });
+ gHandlerService.asyncInit();
+ Assert.ok(!storeInitialized);
+ gHandlerService.enumerate();
+ Assert.ok(storeInitialized);
+ await assertAllHandlerInfosMatchTestData();
+
+ await unloadHandlerStore();
+});
+
+/**
+ * Test saving and reloading an instance of nsIGIOMimeApp.
+ */
+add_task(async function test_store_gioHandlerApp() {
+ if (!("@mozilla.org/gio-service;1" in Cc)) {
+ info("Skipping test because it does not apply to this platform.");
+ return;
+ }
+
+ // Create dummy exec file that following won't fail because file not found error
+ let dummyHandlerFile = FileUtils.getFile("TmpD", ["dummyHandler"]);
+ dummyHandlerFile.createUnique(
+ Ci.nsIFile.NORMAL_FILE_TYPE,
+ parseInt("777", 8)
+ );
+
+ // Set up an nsIGIOMimeApp instance for testing.
+ let handlerApp = Cc["@mozilla.org/gio-service;1"]
+ .getService(Ci.nsIGIOService)
+ .createAppFromCommand(dummyHandlerFile.path, "Dummy GIO handler");
+ let expectedGIOMimeHandlerApp = {
+ name: handlerApp.name,
+ command: handlerApp.command,
+ };
+
+ await deleteHandlerStore();
+
+ let handlerInfo = getKnownHandlerInfo("example/new");
+ handlerInfo.preferredApplicationHandler = handlerApp;
+ handlerInfo.possibleApplicationHandlers.appendElement(handlerApp);
+ handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp);
+ gHandlerService.store(handlerInfo);
+
+ await unloadHandlerStore();
+
+ let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
+ HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+ type: "example/new",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: false,
+ preferredApplicationHandler: expectedGIOMimeHandlerApp,
+ possibleApplicationHandlers: [expectedGIOMimeHandlerApp, webHandlerApp],
+ });
+
+ await IOUtils.remove(dummyHandlerFile.path);
+
+ // After removing dummyHandlerFile, the handler should disappear from the
+ // list of possibleApplicationHandlers and preferredAppHandler should be null.
+ actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
+ HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
+ type: "example/new",
+ preferredAction: Ci.nsIHandlerInfo.saveToDisk,
+ alwaysAskBeforeHandling: false,
+ preferredApplicationHandler: null,
+ possibleApplicationHandlers: [webHandlerApp],
+ });
+});
diff --git a/uriloader/exthandler/tests/unit/test_punycodeURIs.js b/uriloader/exthandler/tests/unit/test_punycodeURIs.js
new file mode 100644
index 0000000000..949c9914ec
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_punycodeURIs.js
@@ -0,0 +1,126 @@
+/* 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/. */
+
+// Encoded test URI to work on all platforms/independent of file encoding
+const kTestURI = "http://\u65e5\u672c\u8a93.jp/";
+const kExpectedURI = "http://xn--wgv71a309e.jp/";
+const kOutputFile = "result.txt";
+
+// Try several times in case the box we're running on is slow.
+const kMaxCheckExistAttempts = 30; // seconds
+var gCheckExistsAttempts = 0;
+
+const tempDir = do_get_tempdir();
+
+function checkFile() {
+ // This is where we expect the output
+ var tempFile = tempDir.clone();
+ tempFile.append(kOutputFile);
+
+ if (!tempFile.exists()) {
+ if (gCheckExistsAttempts >= kMaxCheckExistAttempts) {
+ do_throw(
+ "Expected File " +
+ tempFile.path +
+ " does not exist after " +
+ kMaxCheckExistAttempts +
+ " seconds"
+ );
+ } else {
+ ++gCheckExistsAttempts;
+ // Wait a bit longer then try again
+ do_timeout(1000, checkFile);
+ return;
+ }
+ }
+
+ // Now read it
+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ var sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ fstream.init(tempFile, -1, 0, 0);
+ sstream.init(fstream);
+
+ // Read the first line only as that's the one we expect WriteArguments
+ // to be writing the argument to.
+ var data = sstream.read(4096);
+
+ sstream.close();
+ fstream.close();
+
+ // Now remove the old file
+ tempFile.remove(false);
+
+ // This currently fails on Mac with an argument like -psn_0_nnnnnn
+ // This seems to be to do with how the executable is called, but I couldn't
+ // find a way around it.
+ // Additionally the lack of OS detection in xpcshell tests sucks, so we'll
+ // have to check for the argument mac gives us.
+ if (data.substring(0, 7) != "-psn_0_") {
+ Assert.equal(data, kExpectedURI);
+ }
+
+ do_test_finished();
+}
+
+function run_test() {
+ if (mozinfo.os == "mac") {
+ dump("INFO | test_punycodeURIs.js | Skipping test on mac, bug 599475");
+ return;
+ }
+
+ // set up the uri to test with
+ var ioService = Services.io;
+
+ // set up the local handler object
+ var localHandler = Cc[
+ "@mozilla.org/uriloader/local-handler-app;1"
+ ].createInstance(Ci.nsILocalHandlerApp);
+ localHandler.name = "Test Local Handler App";
+
+ // WriteArgument will just dump its arguments to a file for us.
+ var processDir = do_get_cwd();
+ var exe = processDir.clone();
+ exe.append("WriteArgument");
+
+ if (!exe.exists()) {
+ // Maybe we are on windows
+ exe.leafName = "WriteArgument.exe";
+ if (!exe.exists()) {
+ do_throw("Could not locate the WriteArgument tests executable\n");
+ }
+ }
+
+ var outFile = tempDir.clone();
+ outFile.append(kOutputFile);
+
+ // Set an environment variable for WriteArgument to pick up
+ // The Write Argument file needs to know where its libraries are, so
+ // just force the path variable
+ // For mac
+ var greDir = Services.dirsvc.get("GreD", Ci.nsIFile);
+
+ Services.env.set("DYLD_LIBRARY_PATH", greDir.path);
+ // For Linux
+ Services.env.set("LD_LIBRARY_PATH", greDir.path);
+ // XXX: handle windows
+
+ // Now tell it where we want the file.
+ Services.env.set("WRITE_ARGUMENT_FILE", outFile.path);
+
+ var uri = ioService.newURI(kTestURI);
+
+ // Just check we've got these matching, if we haven't there's a problem
+ // with ascii spec or our test case.
+ Assert.equal(uri.asciiSpec, kExpectedURI);
+
+ localHandler.executable = exe;
+ localHandler.launchWithURI(uri);
+
+ do_test_pending();
+ do_timeout(1000, checkFile);
+}
diff --git a/uriloader/exthandler/tests/unit/xpcshell.ini b/uriloader/exthandler/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..57215d1dc5
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/xpcshell.ini
@@ -0,0 +1,40 @@
+[DEFAULT]
+head = head.js
+run-sequentially = Bug 912235 - Intermittent failures
+firefox-appdir = browser
+
+[test_defaults_handlerService.js]
+# No default stored handlers on android given lack of support.
+# No default stored handlers on Thunderbird.
+skip-if =
+ os == "android"
+ appname == "thunderbird"
+[test_downloads_improvements_migration.js]
+# No default stored handlers on android given lack of support.
+# No default stored handlers on Thunderbird.
+skip-if =
+ os == "android"
+ appname == "thunderbird"
+[test_filename_sanitize.js]
+skip-if =
+ os == 'mac' # Bug 1817727
+[test_getFromTypeAndExtension.js]
+[test_getMIMEInfo_pdf.js]
+[test_getMIMEInfo_unknown_mime_type.js]
+run-if = os == "win" # Windows only test
+[test_getTypeFromExtension_ext_to_type_mapping.js]
+[test_getTypeFromExtension_with_empty_Content_Type.js]
+run-if = os == "win" # Windows only test
+[test_badMIMEType.js]
+run-if = buildapp == "browser"
+[test_handlerService.js]
+support-files = mailcap
+# Bug 676997: test consistently fails on Android
+fail-if = os == "android"
+[test_handlerService_store.js]
+# Disabled for 1563343 -- the app should determine possible handlers in GV.
+fail-if = os == "android"
+support-files = handlers.json
+[test_punycodeURIs.js]
+skip-if =
+ os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1809485