diff options
Diffstat (limited to '')
14 files changed, 2206 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..40e88f930a --- /dev/null +++ b/uriloader/exthandler/tests/unit/handlers.json @@ -0,0 +1,90 @@ +{ + "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" + } + ] + } + } +} diff --git a/uriloader/exthandler/tests/unit/head.js b/uriloader/exthandler/tests/unit/head.js new file mode 100644 index 0000000000..3330a309be --- /dev/null +++ b/uriloader/exthandler/tests/unit/head.js @@ -0,0 +1,79 @@ +/* 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.import( + "resource://gre/modules/AppConstants.jsm" +); +var { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm"); +var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); +var { OS, require } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +var { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +ChromeUtils.import( + "resource://testing-common/HandlerServiceTestUtils.jsm", + this +); +var { TestUtils } = ChromeUtils.import( + "resource://testing-common/TestUtils.jsm" +); + +XPCOMUtils.defineLazyServiceGetter( + this, + "gHandlerService", + "@mozilla.org/uriloader/handler-service;1", + "nsIHandlerService" +); + +do_get_profile(); + +let jsonPath = OS.Path.join(OS.Constants.Path.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 OS.File.remove(jsonPath, { ignoreAbsent: true }); +}; + +/** + * Unloads the data store and replaces it with the test data file. + */ +let copyTestDataToHandlerStore = async function() { + await unloadHandlerStore(); + + await OS.File.copy(do_get_file("handlers.json").path, jsonPath); +}; + +/** + * 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..f9f9feda23 --- /dev/null +++ b/uriloader/exthandler/tests/unit/test_defaults_handlerService.js @@ -0,0 +1,163 @@ +/* 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" +); + +const kDefaultHandlerList = Services.prefs + .getChildList("gecko.handlerService.schemes") + .filter(p => { + try { + let val = Services.prefs.getComplexValue(p, Ci.nsIPrefLocalizedString) + .data; + return !!val; + } catch (ex) { + return false; + } + }); + +add_task(async function test_check_defaults_get_added() { + let protocols = new Set( + kDefaultHandlerList.map(p => p.match(/schemes\.(\w+)/)[1]) + ); + for (let protocol of protocols) { + const kPrefStr = `schemes.${protocol}.`; + let matchingPrefs = kDefaultHandlerList.filter(p => p.includes(kPrefStr)); + let protocolHandlerCount = matchingPrefs.length / 2; + Assert.ok( + protocolHandlerCount, + `Prefs for ${protocol} have at least 1 protocol handler` + ); + 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() { + let mailtoHandlerCount = + kDefaultHandlerList.filter(p => p.includes("mailto")).length / 2; + Assert.ok(mailtoHandlerCount, "Prefs have at least 1 mailto handler"); + 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(); +}); + +/** + * Check that we don't add bogus handlers. + */ +add_task(async function test_check_restrictions() { + const kTestData = { + testdeleteme: [ + ["Delete me", ""], + ["Delete me insecure", "http://example.com/%s"], + ["Delete me no substitution", "https://example.com/"], + ["Keep me", "https://example.com/%s"], + ], + testreallydeleteme: [ + // used to check we remove the entire entry. + ["Delete me", "http://example.com/%s"], + ], + }; + for (let [scheme, handlers] of Object.entries(kTestData)) { + let count = 1; + for (let [name, uriTemplate] of handlers) { + let pref = `gecko.handlerService.schemes.${scheme}.${count}.`; + let obj = Cc["@mozilla.org/pref-localizedstring;1"].createInstance( + Ci.nsIPrefLocalizedString + ); + obj.data = name; + Services.prefs.setComplexValue( + pref + "name", + Ci.nsIPrefLocalizedString, + obj + ); + obj.data = uriTemplate; + Services.prefs.setComplexValue( + pref + "uriTemplate", + Ci.nsIPrefLocalizedString, + obj + ); + count++; + } + } + + gHandlerService.wrappedJSObject._injectDefaultProtocolHandlers(); + let schemeData = gHandlerService.wrappedJSObject._store.data.schemes; + + Assert.ok(schemeData.testdeleteme, "Expect an entry for testdeleteme"); + Assert.ok( + schemeData.testdeleteme.stubEntry, + "Expect a stub entry for testdeleteme" + ); + + Assert.deepEqual( + schemeData.testdeleteme.handlers, + [null, { name: "Keep me", uriTemplate: "https://example.com/%s" }], + "Expect only one handler is kept." + ); + + Assert.ok(!schemeData.testreallydeleteme, "No entry for reallydeleteme"); +}); 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..62031da1e6 --- /dev/null +++ b/uriloader/exthandler/tests/unit/test_getMIMEInfo_pdf.js @@ -0,0 +1,36 @@ +/* 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" +); +XPCOMUtils.defineLazyServiceGetter( + this, + "gBundleService", + "@mozilla.org/intl/stringbundle;1", + "nsIStringBundleService" +); + +// 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 = gBundleService.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..dad5530856 --- /dev/null +++ b/uriloader/exthandler/tests/unit/test_getTypeFromExtension_with_empty_Content_Type.js @@ -0,0 +1,190 @@ +/* -*- 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; + + info("Create a mock RegKey factory"); + let originalRegKey = Cc["@mozilla.org/windows-registry-key;1"].createInstance( + Ci.nsIWindowsRegKey + ); + let mockWindowsRegKeyFactory = { + createInstance(outer, iid) { + if (outer != null) { + throw Components.Exception("", Cr.NS_ERROR_NO_AGGREGATION); + } + 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 + ); + }); +} diff --git a/uriloader/exthandler/tests/unit/test_handlerService.js b/uriloader/exthandler/tests/unit/test_handlerService.js new file mode 100644 index 0000000000..610eb5b749 --- /dev/null +++ b/uriloader/exthandler/tests/unit/test_handlerService.js @@ -0,0 +1,474 @@ +/* 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; + + const env = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment + ); + + const rootPrefBranch = prefSvc.getBranch(""); + + 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.ok(handlerInfo.alwaysAskBeforeHandling); + + // 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, ""); + + // test some default protocol info properties + var haveDefaultHandlersVersion = false; + try { + // If we have a defaultHandlersVersion pref, then assume that we're in the + // firefox tree and that we'll also have default handlers. + // Bug 395131 has been filed to make this test work more generically + // by providing our own prefs for this test rather than this icky + // special casing. + rootPrefBranch.getCharPref("gecko.handlerService.defaultHandlersVersion"); + haveDefaultHandlersVersion = true; + } catch (ex) {} + + 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); + 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 (haveDefaultHandlersVersion) { + Assert.equal(2, protoInfo.possibleApplicationHandlers.length); + } else { + Assert.equal(0, 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 (haveDefaultHandlersVersion) { + Assert.equal(2, 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. + } else { + Assert.equal(0, protoInfo.possibleApplicationHandlers.length); + Assert.ok(protoInfo.alwaysAskBeforeHandling); + } + + if (haveDefaultHandlersVersion) { + // 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"); + Assert.equal(2, 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"]; + if (haveDefaultHandlersVersion) { + handlerTypes.push("mailto"); + handlerTypes.push("irc"); + handlerTypes.push("ircs"); + } + 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") { + env.set("PERSONAL_MAILCAP", do_get_file("mailcap").path); + handlerInfo = mimeSvc.getFromTypeAndExtension("text/plain", null); + Assert.equal( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.useSystemDefault + ); + 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..aa2efde822 --- /dev/null +++ b/uriloader/exthandler/tests/unit/test_handlerService_store.js @@ -0,0 +1,771 @@ +/* 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 alwaysAsk or has an unknown value, but the + * action always becomes useHelperApp when reloading. + */ +add_task(async function test_store_preferredAction() { + await deleteHandlerStore(); + + let handlerInfo = getKnownHandlerInfo("example/new"); + + for (let preferredAction of [Ci.nsIHandlerInfo.alwaysAsk, 999]) { + handlerInfo.preferredAction = preferredAction; + gHandlerService.store(handlerInfo); + gHandlerService.fillHandlerInfo(handlerInfo, ""); + Assert.equal(handlerInfo.preferredAction, Ci.nsIHandlerInfo.useHelperApp); + } +}); + +/** + * 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(); + + for (let type of ["irc", "ircs"]) { + HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), { + type, + preferredActionOSDependent: true, + possibleApplicationHandlers: [ + { + name: "Mibbit", + uriTemplate: "https://www.mibbit.com/?url=%s", + }, + ], + }); + } + + HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), { + type: "mailto", + preferredActionOSDependent: true, + possibleApplicationHandlers: [ + { + name: "Yahoo! Mail", + uriTemplate: "https://compose.mail.yahoo.com/?To=%s", + }, + { + 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(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(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(); + + // Remove the "irc" handler so we can verify that the injection is repeated. + let ircHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("irc"); + gHandlerService.remove(ircHandlerInfo); + + let originalDefaultHandlersVersion = Services.prefs.getComplexValue( + "gecko.handlerService.defaultHandlersVersion", + Ci.nsIPrefLocalizedString + ); + + // Set the preference to an arbitrarily high number to force injecting again. + Services.prefs.setStringPref( + "gecko.handlerService.defaultHandlersVersion", + "999" + ); + + await unloadHandlerStore(); + + // Check that "irc" exists to make sure that the injection was repeated. + Assert.ok(gHandlerService.exists(ircHandlerInfo)); + + // There should be no duplicate handlers in the protocols. + await assertAllHandlerInfosMatchDefaultHandlers(); + + Services.prefs.setStringPref( + "gecko.handlerService.defaultHandlersVersion", + originalDefaultHandlersVersion + ); +}); + +/** + * 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 = JSON.parse(new TextDecoder().decode(await OS.File.read(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 OS.File.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_protocol_ask_dialog_telemetry.js b/uriloader/exthandler/tests/unit/test_protocol_ask_dialog_telemetry.js new file mode 100644 index 0000000000..161cce8d33 --- /dev/null +++ b/uriloader/exthandler/tests/unit/test_protocol_ask_dialog_telemetry.js @@ -0,0 +1,117 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { ContentDispatchChooserTelemetry } = ChromeUtils.import( + "resource://gre/modules/ContentDispatchChooser.jsm" +); + +let telemetryLabels = Services.telemetry.getCategoricalLabels() + .EXTERNAL_PROTOCOL_HANDLER_DIALOG_CONTEXT_SCHEME; + +let schemeToLabel = ContentDispatchChooserTelemetry.SCHEME_TO_LABEL; +let schemePrefixToLabel = + ContentDispatchChooserTelemetry.SCHEME_PREFIX_TO_LABEL; + +/** + * Test for scheme-label mappings of protocol ask dialog telemetry. + */ +add_task(async function test_telemetry_label_maps() { + let mapValues = Object.values(schemeToLabel).concat( + Object.values(schemePrefixToLabel) + ); + + // Scheme - label maps must have valid label values. + mapValues.forEach(label => { + // Mapped labels must be valid. + Assert.ok(telemetryLabels.includes(label), `Exists label: ${label}`); + }); + + // Uppercase labels must have a mapping. + telemetryLabels.forEach(label => { + Assert.equal( + label == "OTHER" || mapValues.includes(label), + label == label.toUpperCase(), + `Exists label: ${label}` + ); + }); + + Object.keys(schemeToLabel).forEach(key => { + // Schemes which have a mapping must not exist as as label. + Assert.ok(!telemetryLabels.includes(key), `Not exists label: ${key}`); + + // There must be no key duplicates across the two maps. + Assert.ok(!schemePrefixToLabel[key], `No duplicate key: ${key}`); + }); +}); + +/** + * Tests the getTelemetryLabel method. + */ +add_task(async function test_telemetry_getTelemetryLabel() { + // Method should return the correct mapping. + Object.keys(schemeToLabel).forEach(scheme => { + Assert.equal( + schemeToLabel[scheme], + ContentDispatchChooserTelemetry._getTelemetryLabel(scheme) + ); + }); + + // Passing null to _getTelemetryLabel should throw. + Assert.throws(() => { + ContentDispatchChooserTelemetry._getTelemetryLabel(null); + }, /Invalid scheme/); + + // Replace maps with test data + ContentDispatchChooserTelemetry.SCHEME_TO_LABEL = { + foo: "FOOLABEL", + bar: "BARLABEL", + }; + + ContentDispatchChooserTelemetry.SCHEME_PREFIX_TO_LABEL = { + fooPrefix: "FOOPREFIXLABEL", + barPrefix: "BARPREFIXLABEL", + fo: "PREFIXLABEL", + }; + + Assert.equal( + ContentDispatchChooserTelemetry._getTelemetryLabel("foo"), + "FOOLABEL", + "Non prefix mapping should have priority" + ); + + Assert.equal( + ContentDispatchChooserTelemetry._getTelemetryLabel("bar"), + "BARLABEL", + "Should return the correct label" + ); + + Assert.equal( + ContentDispatchChooserTelemetry._getTelemetryLabel("fooPrefix"), + "FOOPREFIXLABEL", + "Should return the correct label" + ); + + Assert.equal( + ContentDispatchChooserTelemetry._getTelemetryLabel("fooPrefix1"), + "FOOPREFIXLABEL", + "Should return the correct label" + ); + + Assert.equal( + ContentDispatchChooserTelemetry._getTelemetryLabel("fooPrefix2"), + "FOOPREFIXLABEL", + "Should return the correct label" + ); + + Assert.equal( + ContentDispatchChooserTelemetry._getTelemetryLabel("doesnotexist"), + "OTHER", + "Should return the correct label for unknown scheme" + ); + + // Restore maps + ContentDispatchChooserTelemetry.SCHEME_TO_LABEL = schemeToLabel; + ContentDispatchChooserTelemetry.SCHEME_PREFIX_TO_LABEL = schemePrefixToLabel; +}); diff --git a/uriloader/exthandler/tests/unit/test_punycodeURIs.js b/uriloader/exthandler/tests/unit/test_punycodeURIs.js new file mode 100644 index 0000000000..638128d11b --- /dev/null +++ b/uriloader/exthandler/tests/unit/test_punycodeURIs.js @@ -0,0 +1,130 @@ +/* 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 + var envSvc = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment + ); + + // 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); + + envSvc.set("DYLD_LIBRARY_PATH", greDir.path); + // For Linux + envSvc.set("LD_LIBRARY_PATH", greDir.path); + // XXX: handle windows + + // Now tell it where we want the file. + envSvc.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..d7bf9e54d6 --- /dev/null +++ b/uriloader/exthandler/tests/unit/xpcshell.ini @@ -0,0 +1,28 @@ +[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_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] +[test_handlerService.js] +skip-if = (verify && (os == 'win')) +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] +[test_protocol_ask_dialog_telemetry.js] +skip-if = os == "android" # Desktop telemetry |