From 2723e3cda4dbae5f1e423d6a3fb68dae85c3365b Mon Sep 17 00:00:00 2001 From: Paolo Amadini Date: Mon, 24 Apr 2017 11:29:46 +0100 Subject: [PATCH] Bug 1355585 - Streamline the format of "handlers.json", align the implementation, and reorganize tests. r=mak This patch significantly improves the test coverage for both the JSON and RDF back-ends. There is a clearer separation between tests using predefined data and tests for the injection of default handlers. The predefined data includes more significant property combinations, and the JSON is now formatted. Helper functions are renamed for clarity. Functions like "exists" that have different paths for MIME types and protocols are now tested with both, while behaviors that have a single path are now only tested with MIME types for efficiency. The file format is redesigned to be more compact, and all the data is normalized when saving instead of when loading. Duplicates are now handled correctly when saving. MozReview-Commit-ID: JI4I1M0N3lq --HG-- extra : rebase_source : 06920d9be56830f81e3e01cbc97a02983f50c264 extra : source : 3c4b8028ac594ee24760feb96e93bfdab9704e98 --- uriloader/exthandler/nsHandlerService-json.js | 190 +-- uriloader/exthandler/nsHandlerService.js | 12 +- .../tests/HandlerServiceTestUtils.jsm | 17 +- .../tests/unit/common_test_handlerService.js | 1015 +++++++++-------- uriloader/exthandler/tests/unit/handlers.json | 91 +- uriloader/exthandler/tests/unit/head.js | 4 +- .../tests/unit/mimeTypes-android.rdf | 138 ++- uriloader/exthandler/tests/unit/mimeTypes.rdf | 140 ++- .../tests/unit/test_handlerService_json.js | 72 +- .../tests/unit/test_handlerService_rdf.js | 53 +- 10 files changed, 996 insertions(+), 736 deletions(-) diff --git a/uriloader/exthandler/nsHandlerService-json.js b/uriloader/exthandler/nsHandlerService-json.js index 961e654ddad2..fe51db61f3c7 100644 --- a/uriloader/exthandler/nsHandlerService-json.js +++ b/uriloader/exthandler/nsHandlerService-json.js @@ -39,8 +39,7 @@ HandlerService.prototype = { get _store() { if (!this.__store) { this.__store = new JSONFile({ - path: OS.Path.join(OS.Constants.Path.profileDir, - "handlers.json"), + path: OS.Path.join(OS.Constants.Path.profileDir, "handlers.json"), dataPostProcessor: this._dataPostProcessor.bind(this), }); this.__store.ensureDataReady(); @@ -50,21 +49,26 @@ HandlerService.prototype = { }, _dataPostProcessor(data) { - return data.schemes ? data : { version: {}, mimetypes: {}, schemes: {} }; + return data.defaultHandlersVersion ? data : { + defaultHandlersVersion: {}, + mimeTypes: {}, + schemes: {}, + }; }, _updateDB() { try { - let locale = Services.locale.getAppLocaleAsLangTag(); let prefsDefaultHandlersVersion = Number(Services.prefs.getComplexValue( "gecko.handlerService.defaultHandlersVersion", Ci.nsIPrefLocalizedString).data); - let defaultHandlersVersion = this._store.data.version[locale] || 0; - if (defaultHandlersVersion < prefsDefaultHandlersVersion ) { + let defaultHandlersVersion = + this._store.data.defaultHandlersVersion[locale] || 0; + if (defaultHandlersVersion < prefsDefaultHandlersVersion) { this._injectNewDefaults(); - this._store.data.version[locale] = prefsDefaultHandlersVersion; + this._store.data.defaultHandlersVersion[locale] = + prefsDefaultHandlersVersion; } } catch (ex) { Cu.reportError(ex); @@ -121,26 +125,15 @@ HandlerService.prototype = { for (let handlerNumber of Object.keys(schemes[scheme])) { let handlerApp = this.handlerAppFromSerializable(schemes[scheme][handlerNumber]); - if (!this._isInHandlerArray(possibleHandlers, handlerApp)) { - possibleHandlers.appendElement(handlerApp); - } + // If there is already a handler registered with the same template + // URL, the newly added one will be ignored when saving. + possibleHandlers.appendElement(handlerApp, false); } this.store(protoInfo); } }, - _isInHandlerArray(array, handler) { - let enumerator = array.enumerate(); - while (enumerator.hasMoreElements()) { - let handlerApp = enumerator.getNext().QueryInterface(Ci.nsIHandlerApp); - if (handlerApp.equals(handler)) { - return true; - } - } - return false; - }, - _onDBChange() { return Task.spawn(function* () { if (this.__store) { @@ -163,9 +156,9 @@ HandlerService.prototype = { // nsIHandlerService enumerate() { - let handlers = Cc["@mozilla.org/array;1"]. - createInstance(Ci.nsIMutableArray); - for (let type of Object.keys(this._store.data.mimetypes)) { + let handlers = Cc["@mozilla.org/array;1"] + .createInstance(Ci.nsIMutableArray); + for (let type of Object.keys(this._store.data.mimeTypes)) { let handler = gMIMEService.getFromTypeAndExtension(type, null); handlers.appendElement(handler); } @@ -178,46 +171,76 @@ HandlerService.prototype = { // nsIHandlerService store(handlerInfo) { - let handlerObj = { - action: handlerInfo.preferredAction, - askBeforeHandling: handlerInfo.alwaysAskBeforeHandling, - }; + let handlerList = this._getHandlerListByHandlerInfoType(handlerInfo); - let preferredHandler = handlerInfo.preferredApplicationHandler; - if (preferredHandler) { - let serializable = this.handlerAppToSerializable(preferredHandler); - if (serializable) { - handlerObj.preferredHandler = serializable; + // Retrieve an existing entry if present, instead of creating a new one, so + // that we preserve unknown properties for forward compatibility. + let storedHandlerInfo = handlerList[handlerInfo.type]; + if (!storedHandlerInfo) { + storedHandlerInfo = {}; + handlerList[handlerInfo.type] = storedHandlerInfo; + } + + // Only a limited number of preferredAction values is allowed. + if (handlerInfo.preferredAction == Ci.nsIHandlerInfo.saveToDisk || + handlerInfo.preferredAction == Ci.nsIHandlerInfo.useSystemDefault || + handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally) { + storedHandlerInfo.action = handlerInfo.preferredAction; + } else { + storedHandlerInfo.action = Ci.nsIHandlerInfo.useHelperApp; + } + + if (handlerInfo.alwaysAskBeforeHandling) { + storedHandlerInfo.ask = true; + } else { + delete storedHandlerInfo.ask; + } + + // Build a list of unique nsIHandlerInfo instances to process later. + let handlers = []; + if (handlerInfo.preferredApplicationHandler) { + handlers.push(handlerInfo.preferredApplicationHandler); + } + let enumerator = handlerInfo.possibleApplicationHandlers.enumerate(); + while (enumerator.hasMoreElements()) { + let handler = enumerator.getNext().QueryInterface(Ci.nsIHandlerApp); + // If the caller stored duplicate handlers, we save them only once. + if (!handlers.some(h => h.equals(handler))) { + handlers.push(handler); } } - let apps = handlerInfo.possibleApplicationHandlers.enumerate(); - let possibleHandlers = []; - while (apps.hasMoreElements()) { - let handler = apps.getNext().QueryInterface(Ci.nsIHandlerApp); - let serializable = this.handlerAppToSerializable(handler); - if (serializable) { - possibleHandlers.push(serializable); + // If any of the nsIHandlerInfo instances cannot be serialized, it is not + // included in the final list. The first element is always the preferred + // handler, or null if there is none. + let serializableHandlers = + handlers.map(h => this.handlerAppToSerializable(h)).filter(h => h); + if (serializableHandlers.length) { + if (!handlerInfo.preferredApplicationHandler) { + serializableHandlers.unshift(null); } - } - if (possibleHandlers.length) { - handlerObj.possibleHandlers = possibleHandlers; + storedHandlerInfo.handlers = serializableHandlers; + } else { + delete storedHandlerInfo.handlers; } if (this._isMIMEInfo(handlerInfo)) { let extEnumerator = handlerInfo.getFileExtensions(); - let extensions = []; + let extensions = storedHandlerInfo.extensions || []; while (extEnumerator.hasMore()) { - let extension = extEnumerator.getNext(); + let extension = extEnumerator.getNext().toLowerCase(); + // If the caller stored duplicate extensions, we save them only once. if (!extensions.includes(extension)) { extensions.push(extension); } } if (extensions.length) { - handlerObj.fileExtensions = extensions; + storedHandlerInfo.extensions = extensions; + } else { + delete storedHandlerInfo.extensions; } } - this._getHandlerListByHandlerInfoType(handlerInfo)[handlerInfo.type] = handlerObj; + this._store.saveSoon(); }, @@ -230,42 +253,26 @@ HandlerService.prototype = { Cr.NS_ERROR_NOT_AVAILABLE); } - // logic from _retrievePreferredAction of nsHandlerService.js - if (storedHandlerInfo.action == Ci.nsIHandlerInfo.saveToDisk || - storedHandlerInfo.action == Ci.nsIHandlerInfo.useSystemDefault || - storedHandlerInfo.action == Ci.nsIHandlerInfo.handleInternally) { - handlerInfo.preferredAction = storedHandlerInfo.action; - } else { - handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp; - } + handlerInfo.preferredAction = storedHandlerInfo.action; + handlerInfo.alwaysAskBeforeHandling = !!storedHandlerInfo.ask; - let preferHandler = null; - if (storedHandlerInfo.preferredHandler) { - preferHandler = this.handlerAppFromSerializable(storedHandlerInfo.preferredHandler); - } - handlerInfo.preferredApplicationHandler = preferHandler; - if (preferHandler) { - handlerInfo.possibleApplicationHandlers.appendElement(preferHandler); - } - - if (storedHandlerInfo.possibleHandlers) { - for (let handler of storedHandlerInfo.possibleHandlers) { - let possibleHandler = this.handlerAppFromSerializable(handler); - if (possibleHandler && (!preferHandler || - !possibleHandler.equals(preferHandler))) { - handlerInfo.possibleApplicationHandlers.appendElement(possibleHandler); - } + // If the first item is not null, it is also the preferred handler. Since + // we cannot modify the stored array, use a boolean to keep track of this. + let isFirstItem = true; + for (let handler of storedHandlerInfo.handlers || [null]) { + let handlerApp = this.handlerAppFromSerializable(handler || {}); + if (isFirstItem) { + isFirstItem = false; + handlerInfo.preferredApplicationHandler = handlerApp; + } + if (handlerApp) { + handlerInfo.possibleApplicationHandlers.appendElement(handlerApp); } } - // We always store "askBeforeHandling" in the JSON file. Just use this value. - handlerInfo.alwaysAskBeforeHandling = storedHandlerInfo.askBeforeHandling; - - if (this._isMIMEInfo(handlerInfo)) { - if (storedHandlerInfo.fileExtensions) { - for (let extension of storedHandlerInfo.fileExtensions) { - handlerInfo.appendExtension(extension); - } + if (this._isMIMEInfo(handlerInfo) && storedHandlerInfo.extensions) { + for (let extension of storedHandlerInfo.extensions) { + handlerInfo.appendExtension(extension); } } }, @@ -313,19 +320,19 @@ HandlerService.prototype = { if (!file.exists()) { return null; } - handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]. - createInstance(Ci.nsILocalHandlerApp); + handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"] + .createInstance(Ci.nsILocalHandlerApp); handlerApp.executable = file; } catch (ex) { return null; } } else if ("uriTemplate" in handlerObj) { - handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"]. - createInstance(Ci.nsIWebHandlerApp); + handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"] + .createInstance(Ci.nsIWebHandlerApp); handlerApp.uriTemplate = handlerObj.uriTemplate; } else if ("service" in handlerObj) { - handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"]. - createInstance(Ci.nsIDBusHandlerApp); + handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"] + .createInstance(Ci.nsIDBusHandlerApp); handlerApp.service = handlerObj.service; handlerApp.method = handlerObj.method; handlerApp.objectPath = handlerObj.objectPath; @@ -339,11 +346,11 @@ HandlerService.prototype = { }, /** - * The function return a reference to the "mimetypes" or "schemes" object + * The function returns a reference to the "mimeTypes" or "schemes" object * based on which type of handlerInfo is provided. */ _getHandlerListByHandlerInfoType(handlerInfo) { - return this._isMIMEInfo(handlerInfo) ? this._store.data.mimetypes + return this._isMIMEInfo(handlerInfo) ? this._store.data.mimeTypes : this._store.data.schemes; }, @@ -372,16 +379,15 @@ HandlerService.prototype = { // nsIHandlerService getTypeFromExtension(fileExtension) { let extension = fileExtension.toLowerCase(); - let mimeTypes = this._store.data.mimetypes; + let mimeTypes = this._store.data.mimeTypes; for (let type of Object.keys(mimeTypes)) { - if (mimeTypes[type].fileExtensions && - mimeTypes[type].fileExtensions.includes(extension)) { - return type; + if (mimeTypes[type].extensions && + mimeTypes[type].extensions.includes(extension)) { + return type; } } return ""; }, - }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]); diff --git a/uriloader/exthandler/nsHandlerService.js b/uriloader/exthandler/nsHandlerService.js index 0488f30d5a39..10bd5e56e648 100644 --- a/uriloader/exthandler/nsHandlerService.js +++ b/uriloader/exthandler/nsHandlerService.js @@ -709,12 +709,7 @@ HandlerService.prototype = { var handler = aHandlerInfo.preferredApplicationHandler; - if (handler) { - // If the handlerApp is an unknown type, ignore it. - // Android default application handler is the case. - if (this._handlerAppIsUnknownType(handler)) { - return; - } + if (handler && !this._handlerAppIsUnknownType(handler)) { this._storeHandlerApp(handlerID, handler); // Make this app be the preferred app for the handler info. @@ -728,8 +723,9 @@ HandlerService.prototype = { this._setResource(infoID, NC_PREFERRED_APP, handlerID); } else { - // There isn't a preferred handler. Remove the existing record for it, - // if any. + // There isn't a preferred handler or the handler cannot be serialized, + // for example the Android default application handler. Remove the + // existing record for it, if any. this._removeTarget(infoID, NC_PREFERRED_APP); this._removeAssertions(handlerID); } diff --git a/uriloader/exthandler/tests/HandlerServiceTestUtils.jsm b/uriloader/exthandler/tests/HandlerServiceTestUtils.jsm index 8de0f2dec588..be79f00dba3b 100644 --- a/uriloader/exthandler/tests/HandlerServiceTestUtils.jsm +++ b/uriloader/exthandler/tests/HandlerServiceTestUtils.jsm @@ -95,6 +95,14 @@ this.HandlerServiceTestUtils = { // that may have been imported from the default nsIHandlerService instance // and is not overwritten by fillHandlerInfo later. let handlerInfo = gMIMEService.getFromTypeAndExtension(type, null); + if (AppConstants.platform == "android") { + // On Android, the first handler application is always the internal one. + while (handlerInfo.possibleApplicationHandlers.length > 1) { + handlerInfo.possibleApplicationHandlers.removeElementAt(1); + } + } else { + handlerInfo.possibleApplicationHandlers.clear(); + } handlerInfo.setFileExtensions(""); // Populate the object from the handler service instance under testing. if (this.handlerService.exists(handlerInfo)) { @@ -173,9 +181,12 @@ this.HandlerServiceTestUtils = { : Ci.nsIHandlerInfo; Assert.ok(handlerInfo instanceof expectedInterface); Assert.equal(handlerInfo.type, expected.type); - Assert.equal(handlerInfo.preferredAction, expected.preferredAction); - Assert.equal(handlerInfo.alwaysAskBeforeHandling, - expected.alwaysAskBeforeHandling); + + if (!expected.preferredActionOSDependent) { + Assert.equal(handlerInfo.preferredAction, expected.preferredAction); + Assert.equal(handlerInfo.alwaysAskBeforeHandling, + expected.alwaysAskBeforeHandling); + } if (expectedInterface == Ci.nsIMIMEInfo) { let fileExtensionsEnumerator = handlerInfo.getFileExtensions(); diff --git a/uriloader/exthandler/tests/unit/common_test_handlerService.js b/uriloader/exthandler/tests/unit/common_test_handlerService.js index 94de89a5861d..13e35afa9511 100644 --- a/uriloader/exthandler/tests/unit/common_test_handlerService.js +++ b/uriloader/exthandler/tests/unit/common_test_handlerService.js @@ -1,68 +1,57 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -/** - * This script is loaded by "test_handlerService_json.js" and "test_handlerService_rdf.js" - * to make sure there is the same behavior when using two different implementations - * of handlerService (JSON backend and RDF backend). +/* + * Loaded by "test_handlerService_json.js" and "test_handlerService_rdf.js" to + * check that the nsIHandlerService interface has the same behavior with both + * the JSON and RDF backends. */ HandlerServiceTestUtils.handlerService = gHandlerService; -const pdfHandlerInfo = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type"); -const gzipHandlerInfo = - HandlerServiceTestUtils.getHandlerInfo("application/x-gzip"); -const ircHandlerInfo = - HandlerServiceTestUtils.getHandlerInfo("irc"); - -let executable = Services.dirsvc.get("TmpD", Ci.nsIFile); -let localHandler = { - name: "Local Handler", - executable: executable, - interfaces: [Ci.nsIHandlerApp, Ci.nsILocalHandlerApp, Ci.nsISupports], - QueryInterface: function(iid) { - if (!this.interfaces.some( function(v) { return iid.equals(v) } )) - throw Cr.NS_ERROR_NO_INTERFACE; - return this; - } +// 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, }; -let webHandler = { - name: "Web Handler", - uriTemplate: "https://www.webhandler.com/?url=%s", - interfaces: [Ci.nsIHandlerApp, Ci.nsIWebHandlerApp, Ci.nsISupports], - QueryInterface: function(iid) { - if (!this.interfaces.some( function(v) { return iid.equals(v) } )) - throw Cr.NS_ERROR_NO_INTERFACE; - return this; - } +// 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, }; -let dBusHandler = { - name: "DBus Handler", - service: "DBus Service", - method: "DBus method", - objectPath: "/tmp/PATH/DBus", - dBusInterface: "DBusInterface", - interfaces: [Ci.nsIHandlerApp, Ci.nsIDBusHandlerApp, Ci.nsISupports], - QueryInterface: function(iid) { - if (!this.interfaces.some( function(v) { return iid.equals(v) } )) - throw Cr.NS_ERROR_NO_INTERFACE; - return this; - } -}; +/** + * 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; +} -// Loads data from a file in a predefined format. This test verifies that: -// - The JSON format used in previous versions can be loaded -// - All the known properties specified in the JSON file are considered -add_task(function* testLoadPredefined() { - yield prepareImportDB(); - - let handlerInfos = - HandlerServiceTestUtils.getAllHandlerInfos(); +/** + * 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 @@ -70,29 +59,485 @@ add_task(function* testLoadPredefined() { // even if one already exists in the system, resulting in duplicate entries. HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), { - type: "irc", + 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: "Mibbit", - uriTemplate: "https://www.mibbit.com/?url=%s", + 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: "ircs", + type: "examplescheme.usehelperapp", preferredAction: Ci.nsIHandlerInfo.useHelperApp, alwaysAskBeforeHandling: true, + preferredApplicationHandler: { + name: "Example Default Handler", + uriTemplate: "https://www.example.com/?url=%s", + }, possibleApplicationHandlers: [{ - name: "Mibbit", - uriTemplate: "https://www.mibbit.com/?url=%s", + 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", + }], + }); + + do_check_eq(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(function* test_store_fillHandlerInfo_predefined() { + // Test that the file format used in previous versions can be loaded. + yield copyTestDataToHandlerStore(); + yield 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(); + yield 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. + yield unloadHandlerStore(); + yield 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(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. + yield 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; + + yield copyTestDataToHandlerStore(); + + do_check_true(gHandlerService.exists(handlerInfoPresent)); + do_check_false(gHandlerService.exists(handlerInfoAbsent)); + + gHandlerService.store(handlerInfoAbsent); + gHandlerService.remove(handlerInfoPresent); + + yield unloadHandlerStore(); + + do_check_false(gHandlerService.exists(handlerInfoPresent)); + do_check_true(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(function* test_store_preferredAction() { + yield deleteHandlerStore(); + + let handlerInfo = getKnownHandlerInfo("example/new"); + + for (let preferredAction of [Ci.nsIHandlerInfo.alwaysAsk, 999]) { + handlerInfo.preferredAction = preferredAction; + gHandlerService.store(handlerInfo); + gHandlerService.fillHandlerInfo(handlerInfo, ""); + do_check_eq(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(function* test_store_localHandlerApp_missing() { + if (!("@mozilla.org/uriloader/dbus-handler-app;1" in Cc)) { + do_print("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"]); + + yield deleteHandlerStore(); + + let handlerInfo = getKnownHandlerInfo("example/new"); + handlerInfo.preferredApplicationHandler = missingHandlerApp; + handlerInfo.possibleApplicationHandlers.appendElement(missingHandlerApp); + handlerInfo.possibleApplicationHandlers.appendElement(webHandlerApp); + gHandlerService.store(handlerInfo); + + yield 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(function* test_store_dBusHandlerApp() { + if (!("@mozilla.org/uriloader/dbus-handler-app;1" in Cc)) { + do_print("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, + }; + + yield deleteHandlerStore(); + + let handlerInfo = getKnownHandlerInfo("example/new"); + handlerInfo.preferredApplicationHandler = dBusHandlerApp; + handlerInfo.possibleApplicationHandlers.appendElement(dBusHandlerApp); + gHandlerService.store(handlerInfo); + + yield 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(function* test_store_possibleApplicationHandlers_includes_preferred() { + yield deleteHandlerStore(); + + let handlerInfo = getKnownHandlerInfo("example/new"); + handlerInfo.preferredApplicationHandler = localHandlerApp; + gHandlerService.store(handlerInfo); + + yield 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(function* test_store_possibleApplicationHandlers_preferred_first() { + yield 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); + + yield 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(function* test_store_fileExtensions_lowercase() { + yield deleteHandlerStore(); + + let handlerInfo = getKnownHandlerInfo("example/new"); + handlerInfo.appendExtension("extension_test1"); + handlerInfo.appendExtension("EXTENSION_test2"); + gHandlerService.store(handlerInfo); + + yield 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 duplicates added with "appendExtension" or present in + * "possibleApplicationHandlers" are removed when saving and reloading. + */ +add_task(function* test_store_no_duplicates() { + yield 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"); + gHandlerService.store(handlerInfo); + + yield 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 "store" deletes properties that have their default values from + * the data store. This is mainly relevant for the JSON back-end. + * + * File extensions are never deleted once they have been associated. + */ +add_task(function* test_store_deletes_properties_except_extensions() { + yield 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. + yield copyTestDataToHandlerStore(); + gHandlerService.store(handlerInfo); + + // Now we can reload the data and verify that no extra values have been kept. + yield 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(function* test_fillHandlerInfo_overrideType() { + // Test both MIME types and protocols. + for (let type of ["example/type.usesystemdefault", + "examplescheme.usesystemdefault"]) { + yield 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. + yield 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(function* test_getTypeFromExtension() { + yield copyTestDataToHandlerStore(); + + do_check_eq(gHandlerService.getTypeFromExtension(""), ""); + do_check_eq(gHandlerService.getTypeFromExtension("example_unknown"), ""); + do_check_eq(gHandlerService.getTypeFromExtension("example_one"), + "example/type.handleinternally"); + do_check_eq(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", - preferredAction: Ci.nsIHandlerInfo.useSystemDefault, - alwaysAskBeforeHandling: false, + preferredActionOSDependent: true, possibleApplicationHandlers: [{ name: "Yahoo! Mail", uriTemplate: "https://compose.mail.yahoo.com/?To=%s", @@ -102,437 +547,65 @@ add_task(function* testLoadPredefined() { }], }); - HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), { - type: "nonexistent/type", - preferredAction: Ci.nsIHandlerInfo.handleInternally, - alwaysAskBeforeHandling: false, - fileExtensions: ["pdf"], - }); - HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfos.shift(), { type: "webcal", - preferredAction: Ci.nsIHandlerInfo.useHelperApp, - alwaysAskBeforeHandling: true, - preferredApplicationHandler: { - name: "30 Boxes", - uriTemplate: "http://30boxes.com/external/widget?refer=ff&url=%s", - }, + preferredActionOSDependent: true, possibleApplicationHandlers: [{ - name: "30 Boxes", - uriTemplate: "http://30boxes.com/external/widget?refer=ff&url=%s", - },{ name: "30 Boxes", uriTemplate: "https://30boxes.com/external/widget?refer=ff&url=%s", }], }); do_check_eq(handlerInfos.length, 0); -}); +} -// Verify the load mechansim of hander service by -// - Start the hander service with DB -// - Do some modifications on DB first and reload it -add_task(function* testImportAndReload() { - // I. Prepare a testing ds first and do reload for handerService - yield prepareImportDB(); - Assert.deepEqual(HandlerServiceTestUtils.getAllHandlerInfoTypes(), - ["irc", "ircs", "mailto", "nonexistent/type", "webcal"]); - - // II. do modifications first and reload the DS again - gHandlerService.store(gzipHandlerInfo); - gHandlerService.remove(pdfHandlerInfo); - yield reloadData(); - Assert.deepEqual(HandlerServiceTestUtils.getAllHandlerInfoTypes(), - ["application/x-gzip", "irc", "ircs", "mailto", "webcal"]); -}); - -// Verify reload without DB -add_task(function* testReloadWithoutDB() { - yield removeImportDB(); - // If we have a defaultHandlersVersion pref, then assume that we're in the - // firefox tree and that we'll also have default handlers. - if (Services.prefs.getPrefType("gecko.handlerService.defaultHandlersVersion")){ - Assert.deepEqual(HandlerServiceTestUtils.getAllHandlerInfoTypes(), - ["irc", "ircs", "mailto", "webcal"]); - } -}); - -// Do the test for exist() with store() and remove() -add_task(function* testExists() { - yield prepareImportDB(); - - do_check_true(gHandlerService.exists(pdfHandlerInfo)); - do_check_false(gHandlerService.exists(gzipHandlerInfo)); - - // Remove the handler of irc first - let handler = ircHandlerInfo; - gHandlerService.remove(handler); - do_check_false(gHandlerService.exists(handler)); - gHandlerService.store(handler); - do_check_true(gHandlerService.exists(handler)); -}); - -// Do the test for GetTypeFromExtension() with store(), remove() and exist() -add_task(function* testGetTypeFromExtension() { - yield prepareImportDB(); - - let type = gHandlerService.getTypeFromExtension("doc"); - do_check_eq(type, ""); - type = gHandlerService.getTypeFromExtension("pdf"); - do_check_eq(type, "nonexistent/type"); - - // Load the "pdf" extension into the nsIHandlerInfo for "nonexistent/type". - gHandlerService.fillHandlerInfo(pdfHandlerInfo, ""); - - gHandlerService.remove(pdfHandlerInfo); - do_check_false(gHandlerService.exists(pdfHandlerInfo)); - type = gHandlerService.getTypeFromExtension("pdf"); - do_check_eq(type, ""); - - gHandlerService.store(pdfHandlerInfo); - do_check_true(gHandlerService.exists(pdfHandlerInfo)); - type = gHandlerService.getTypeFromExtension("pdf"); - do_check_eq(type, "nonexistent/type"); -}); - -// Test the functionality of fillHandlerInfo : -// - All the detail of handlerinfo are filled perferectly -// - The set of possible handlers included the preferred handler -add_task(function* testStoreAndFillHandlerInfo() { - yield removeImportDB(); - - // 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. - let handlerInfo = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type"); - let handlerInfo2 = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type2"); - handlerInfo2.preferredAction = Ci.nsIHandlerInfo.useSystemDefault; - handlerInfo2.preferredApplicationHandler = localHandler; - handlerInfo2.alwaysAskBeforeHandling = false; - handlerInfo2.appendExtension("type2"); - gHandlerService.store(handlerInfo2); - - gHandlerService.fillHandlerInfo(handlerInfo, "nonexistent/type2"); - HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfo, { - type: "nonexistent/type", - preferredAction: Ci.nsIHandlerInfo.useSystemDefault, - alwaysAskBeforeHandling: false, - fileExtensions: ["type2"], - preferredApplicationHandler: { - name: "Local Handler", - executable, - }, - possibleApplicationHandlers: [{ - name: "Local Handler", - executable, - }], - }); -}); - -// Test the functionality of fillHandlerInfo : -// - Check the failure case by requesting a non-existent handler type -add_task(function* testFillHandlerInfoWithError() { - yield removeImportDB(); - - let handlerInfo = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type"); - - Assert.throws( - () => gHandlerService.fillHandlerInfo(handlerInfo, "nonexistent/type2"), - ex => ex.result == Cr.NS_ERROR_NOT_AVAILABLE); -}); - -// Test the functionality of fillHandlerInfo : -// - Prefer handler is the first one of possibleHandlers and with only one instance -add_task(function* testPreferHandlerIsTheFirstOrder() { - yield removeImportDB(); - - let handlerInfo = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type"); - let handlerInfo2 = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type2"); - handlerInfo2.preferredAction = Ci.nsIHandlerInfo.useHelperApp; - handlerInfo2.preferredApplicationHandler = webHandler; - handlerInfo2.possibleApplicationHandlers.appendElement(localHandler); - handlerInfo2.possibleApplicationHandlers.appendElement(webHandler); - handlerInfo2.alwaysAskBeforeHandling = false; - gHandlerService.store(handlerInfo2); - - gHandlerService.fillHandlerInfo(handlerInfo, "nonexistent/type2"); - HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfo, { - type: "nonexistent/type", - preferredAction: Ci.nsIHandlerInfo.useHelperApp, - alwaysAskBeforeHandling: false, - preferredApplicationHandler: { - name: webHandler.name, - uriTemplate: webHandler.uriTemplate, - }, - possibleApplicationHandlers: [{ - name: webHandler.name, - uriTemplate: webHandler.uriTemplate, - },{ - name: "Local Handler", - executable, - }], - }); -}); - -// Verify the handling of app handler: web handler -add_task(function* testStoreForWebHandler() { - yield removeImportDB(); - - let handlerInfo = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type"); - let handlerInfo2 = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type2"); - handlerInfo2.preferredAction = Ci.nsIHandlerInfo.useHelperApp; - handlerInfo2.preferredApplicationHandler = webHandler; - handlerInfo2.alwaysAskBeforeHandling = false; - gHandlerService.store(handlerInfo2); - - gHandlerService.fillHandlerInfo(handlerInfo, "nonexistent/type2"); - HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfo, { - type: "nonexistent/type", - preferredAction: Ci.nsIHandlerInfo.useHelperApp, - alwaysAskBeforeHandling: false, - preferredApplicationHandler: { - name: webHandler.name, - uriTemplate: webHandler.uriTemplate, - }, - possibleApplicationHandlers: [{ - name: webHandler.name, - uriTemplate: webHandler.uriTemplate, - }], - }); -}); - -// Verify the handling of app handler: DBus handler -add_task(function* testStoreForDBusHandler() { - if (!("@mozilla.org/uriloader/dbus-handler-app;1" in Cc)) { - do_print("Skipping test because it does not apply to this platform."); - return; - } - - yield removeImportDB(); - - let handlerInfo = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type"); - let handlerInfo2 = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type2"); - handlerInfo2.preferredAction = Ci.nsIHandlerInfo.useHelperApp; - handlerInfo2.preferredApplicationHandler = dBusHandler; - handlerInfo2.alwaysAskBeforeHandling = false; - gHandlerService.store(handlerInfo2); - - gHandlerService.fillHandlerInfo(handlerInfo, "nonexistent/type2"); - let expectedHandler = { - name: dBusHandler.name, - service: dBusHandler.service, - method: dBusHandler.method, - dBusInterface: dBusHandler.dBusInterface, - objectPath: dBusHandler.objectPath, - }; - HandlerServiceTestUtils.assertHandlerInfoMatches(handlerInfo, { - type: "nonexistent/type", - preferredAction: Ci.nsIHandlerInfo.useHelperApp, - alwaysAskBeforeHandling: false, - preferredApplicationHandler: expectedHandler, - possibleApplicationHandlers: [expectedHandler], - }); -}); - -// Test the functionality of _IsInHandlerArray() by injecting default handler again -// Since we don't have defaultHandlersVersion pref on Android, skip this test. -// Also skip for applications like Thunderbird which don't have all the prefs. -add_task(function* testIsInHandlerArray() { - if (AppConstants.platform == "android") { - do_print("Skipping test because it does not apply to this platform."); - return; - } +/** + * Tests the default protocol handlers imported from the locale-specific data. + */ +add_task(function* test_default_protocol_handlers() { if (!Services.prefs.getPrefType("gecko.handlerService.defaultHandlersVersion")) { - do_print("Skipping test: No pref gecko.handlerService.defaultHandlersVersion."); + do_print("This platform or locale does not have default handlers."); return; } - yield removeImportDB(); + // This will inject the default protocol handlers for the current locale. + yield deleteHandlerStore(); - let protoInfo = HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent"); - do_check_eq(protoInfo.possibleApplicationHandlers.length, 0); - gHandlerService.fillHandlerInfo(protoInfo, "ircs"); - do_check_eq(protoInfo.possibleApplicationHandlers.length, 1); + yield assertAllHandlerInfosMatchDefaultHandlers(); +}); - // Remove the handler of irc first - let ircInfo = HandlerServiceTestUtils.getHandlerInfo("irc"); - gHandlerService.remove(ircInfo); - do_check_false(gHandlerService.exists(ircInfo)); +/** + * Tests that the default protocol handlers are not imported again from the + * locale-specific data if they already exist. + */ +add_task(function* test_default_protocol_handlers_no_duplicates() { + if (!Services.prefs.getPrefType("gecko.handlerService.defaultHandlersVersion")) { + do_print("This platform or locale does not have default handlers."); + return; + } - let origPrefs = Services.prefs.getComplexValue( + // This will inject the default protocol handlers for the current locale. + yield 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 preference as an arbitrarily high number to force injecting - let string = Cc["@mozilla.org/pref-localizedstring;1"] - .createInstance(Ci.nsIPrefLocalizedString); - string.data = "999"; - Services.prefs.setComplexValue("gecko.handlerService.defaultHandlersVersion", - Ci.nsIPrefLocalizedString, string); + // Set the preference to an arbitrarily high number to force injecting again. + Services.prefs.setStringPref("gecko.handlerService.defaultHandlersVersion", + "999"); - // do reloading - yield reloadData(); + yield unloadHandlerStore(); - // check "irc" exists again to make sure that injection actually happened - do_check_true(gHandlerService.exists(ircInfo)); + // Check that "irc" exists to make sure that the injection was repeated. + do_check_true(gHandlerService.exists(ircHandlerInfo)); - // test "ircs" has only one handler to know the _IsInHandlerArray was invoked - protoInfo = HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent"); - do_check_false(gHandlerService.exists(protoInfo)); - gHandlerService.fillHandlerInfo(protoInfo, "ircs"); - do_check_eq(protoInfo.possibleApplicationHandlers.length, 1); + // There should be no duplicate handlers in the protocols. + yield assertAllHandlerInfosMatchDefaultHandlers(); - // reset the preference after the test - Services.prefs.setComplexValue("gecko.handlerService.defaultHandlersVersion", - Ci.nsIPrefLocalizedString, origPrefs); -}); - -// Test the basic functionality of FillHandlerInfo() for protocol -// Since Android use mimeInfo to deal with mimeTypes and protocol, skip this test. -// Also skip for applications like Thunderbird which don't have all the prefs. -add_task(function* testFillHandlerInfoForProtocol() { - if (AppConstants.platform == "android") { - do_print("Skipping test because it does not apply to this platform."); - return; - } - if (!Services.prefs.getPrefType("gecko.handlerService.defaultHandlersVersion")) { - do_print("Skipping test: No pref gecko.handlerService.defaultHandlersVersion."); - return; - } - - yield removeImportDB(); - - let protoInfo = HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent"); - - let ircInfo = HandlerServiceTestUtils.getHandlerInfo("irc"); - do_check_true(gHandlerService.exists(ircInfo)); - - gHandlerService.fillHandlerInfo(protoInfo, "irc"); - HandlerServiceTestUtils.assertHandlerInfoMatches(protoInfo, { - type: "nonexistent", - preferredAction: Ci.nsIHandlerInfo.useHelperApp, - alwaysAskBeforeHandling: true, - possibleApplicationHandlers: [{ - name: "Mibbit", - uriTemplate: "https://www.mibbit.com/?url=%s", - }], - }); -}); - - -// Test the functionality of store() and fillHandlerInfo for protocol -add_task(function* testStoreForProtocol() { - yield removeImportDB(); - - let protoInfo = HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent"); - let protoInfo2 = HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent2"); - protoInfo2.preferredAction = Ci.nsIHandlerInfo.useHelperApp; - protoInfo2.alwaysAskBeforeHandling = false; - protoInfo2.preferredApplicationHandler = webHandler; - gHandlerService.store(protoInfo2); - - yield reloadData(); - do_check_true(gHandlerService.exists(protoInfo2)); - - gHandlerService.fillHandlerInfo(protoInfo, "nonexistent2"); - HandlerServiceTestUtils.assertHandlerInfoMatches(protoInfo, { - type: "nonexistent", - preferredAction: Ci.nsIHandlerInfo.useHelperApp, - alwaysAskBeforeHandling: false, - preferredApplicationHandler: { - name: webHandler.name, - uriTemplate: webHandler.uriTemplate, - }, - possibleApplicationHandlers: [{ - name: webHandler.name, - uriTemplate: webHandler.uriTemplate, - }], - }); -}); - -// Test the functionality of fillHandlerInfo when there is no overrideType -add_task(function* testFillHandlerInfoWithoutOverrideType() { - yield removeImportDB(); - - // mimeType - let mimeInfo = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type"); - let storedHandlerInfo = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent/type"); - storedHandlerInfo.preferredAction = Ci.nsIHandlerInfo.useSystemDefault; - storedHandlerInfo.preferredApplicationHandler = webHandler; - storedHandlerInfo.alwaysAskBeforeHandling = false; - gHandlerService.store(storedHandlerInfo); - - // protocol type - let protoInfo = HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent"); - let storedProtoInfo = - HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent"); - storedProtoInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp; - storedProtoInfo.alwaysAskBeforeHandling = false; - storedProtoInfo.preferredApplicationHandler = webHandler; - gHandlerService.store(storedProtoInfo); - - // Get handlerInfo by fillHandlerInfo without overrideType - for (let handlerInfo of [mimeInfo, protoInfo]) { - let handlerInfo2 = storedProtoInfo; - if (handlerInfo.type == "nonexistent/type") { - handlerInfo2 = storedHandlerInfo; - } - gHandlerService.fillHandlerInfo(handlerInfo, null); - do_check_eq(handlerInfo.preferredAction, handlerInfo2.preferredAction); - do_check_eq(handlerInfo.alwaysAskBeforeHandling, - handlerInfo2.alwaysAskBeforeHandling); - do_check_eq(handlerInfo.preferredApplicationHandler.name, - handlerInfo2.preferredApplicationHandler.name); - let apps = handlerInfo.possibleApplicationHandlers.enumerate(); - let app; - if (AppConstants.platform == "android") { - app = apps.getNext().QueryInterface(Ci.nsIHandlerApp); - do_check_eq(app.name, "Android chooser"); - } - app = apps.getNext().QueryInterface(Ci.nsIWebHandlerApp); - do_check_eq(app.name, webHandler.name); - do_check_eq(app.uriTemplate, webHandler.uriTemplate); - } -}); - -// Test the functionality of fillHandlerInfo() : -// - Use "nsIHandlerInfo.useHelperApp" to replace "nsIHandlerInfo.alwaysAsk" for handlerInfo.preferredAction -// - Use "nsIHandlerInfo.useHelperApp" to replace unknow action for handlerInfo.preferredAction -add_task(function* testPreferredActionHandling() { - yield removeImportDB(); - - let protoInfo = HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent"); - let protoInfo2 = HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent2"); - - for (let preferredAction of [ - Ci.nsIHandlerInfo.saveToDisk, - Ci.nsIHandlerInfo.useHelperApp, - Ci.nsIHandlerInfo.handleInternally, - Ci.nsIHandlerInfo.useSystemDefault - ]) { - protoInfo2.preferredAction = preferredAction; - gHandlerService.store(protoInfo2); - gHandlerService.fillHandlerInfo(protoInfo, "nonexistent2"); - do_check_eq(protoInfo.preferredAction, preferredAction); - } - - for (let preferredAction of [ - Ci.nsIHandlerInfo.alwaysAsk, - 999 - ]) { - protoInfo2.preferredAction = preferredAction; - gHandlerService.store(protoInfo2); - gHandlerService.fillHandlerInfo(protoInfo, "nonexistent2"); - do_check_eq(protoInfo.preferredAction, Ci.nsIHandlerInfo.useHelperApp); - } + Services.prefs.setStringPref("gecko.handlerService.defaultHandlersVersion", + originalDefaultHandlersVersion); }); diff --git a/uriloader/exthandler/tests/unit/handlers.json b/uriloader/exthandler/tests/unit/handlers.json index 93e39b8f98cb..40e88f930ae8 100644 --- a/uriloader/exthandler/tests/unit/handlers.json +++ b/uriloader/exthandler/tests/unit/handlers.json @@ -1 +1,90 @@ -{"version":{"en-US":999},"mimetypes":{"nonexistent/type":{"action":3,"askBeforeHandling":false,"fileExtensions":["pdf"]}},"schemes":{"webcal":{"action":1,"askBeforeHandling":true,"preferredHandler":{"name":"30 Boxes","uriTemplate":"http://30boxes.com/external/widget?refer=ff&url=%s"},"possibleHandlers":[{"name":"30 Boxes","uriTemplate":"https://30boxes.com/external/widget?refer=ff&url=%s"}]},"ircs":{"action":1,"askBeforeHandling":true,"fileExtensions":[],"possibleHandlers":[{"name":"Mibbit","uriTemplate":"https://www.mibbit.com/?url=%s"}]},"mailto":{"action":4,"askBeforeHandling":false,"possibleHandlers":[{"name":"Yahoo! Mail","uriTemplate":"https://compose.mail.yahoo.com/?To=%s"},{"name":"Gmail","uriTemplate":"https://mail.google.com/mail/?extsrc=mailto&url=%s"}]},"irc":{"action":1,"askBeforeHandling":true,"possibleHandlers":[{"name":"Mibbit","uriTemplate":"https://www.mibbit.com/?url=%s"}]}}} +{ + "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 index 4244d3ef5dd4..b47c632fb908 100644 --- a/uriloader/exthandler/tests/unit/head.js +++ b/uriloader/exthandler/tests/unit/head.js @@ -11,6 +11,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; Cu.import("resource://gre/modules/AppConstants.jsm"); +Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/Services.jsm"); @@ -26,8 +27,7 @@ do_get_profile(); let jsonPath = OS.Path.join(OS.Constants.Path.profileDir, "handlers.json"); -let rdfFile = Services.dirsvc.get("ProfD", Ci.nsIFile); -rdfFile.append("mimeTypes.rdf") +let rdfFile = FileUtils.getFile("ProfD", ["mimeTypes.rdf"]); function deleteDatasourceFile() { if (rdfFile.exists()) { diff --git a/uriloader/exthandler/tests/unit/mimeTypes-android.rdf b/uriloader/exthandler/tests/unit/mimeTypes-android.rdf index 13b59b07649c..a3df9a5bf029 100644 --- a/uriloader/exthandler/tests/unit/mimeTypes-android.rdf +++ b/uriloader/exthandler/tests/unit/mimeTypes-android.rdf @@ -1,78 +1,100 @@ - - - - - - - - - - - - - - - - - - + + + + + + - + + example_two + example_three + + + - - - - + + - + + + + + + + + + + + example_two + example_three + + + + + + + + + + + + + - - + - - + + - - - - - - - - - - + + + + + + + + + diff --git a/uriloader/exthandler/tests/unit/mimeTypes.rdf b/uriloader/exthandler/tests/unit/mimeTypes.rdf index 09ec8baedcb6..682453569eff 100644 --- a/uriloader/exthandler/tests/unit/mimeTypes.rdf +++ b/uriloader/exthandler/tests/unit/mimeTypes.rdf @@ -1,83 +1,105 @@ - - + + + - - - - - - - - - - + + + + - + - - + - - + + + example_two + example_three - - - + + - - - - + + + + + + + - - + + - + + + + + + + - + + + + - - - - + + - - - - + + + + - - + + + + + + + + example_two + example_three + diff --git a/uriloader/exthandler/tests/unit/test_handlerService_json.js b/uriloader/exthandler/tests/unit/test_handlerService_json.js index 40e9b9e15fa6..4bcd9d8bc37c 100644 --- a/uriloader/exthandler/tests/unit/test_handlerService_json.js +++ b/uriloader/exthandler/tests/unit/test_handlerService_json.js @@ -1,35 +1,65 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -/** - * Tests the handlerService interfaces using JSON backend. +/* + * Tests the nsIHandlerService interface using the JSON backend. */ XPCOMUtils.defineLazyServiceGetter(this, "gHandlerService", "@mozilla.org/uriloader/handler-service-json;1", "nsIHandlerService"); -var scriptFile = do_get_file("common_test_handlerService.js"); -Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec); +/** + * 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 = Task.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; -var prepareImportDB = Task.async(function* () { - yield reloadData(); - - yield OS.File.copy(do_get_file("handlers.json").path, jsonPath); -}); - -var removeImportDB = Task.async(function* () { - yield reloadData(); - - yield OS.File.remove(jsonPath, { ignoreAbsent: true }); -}); - -var reloadData = Task.async(function* () { - // Force the initialization of handlerService to prevent observer is not initialized yet. - let svc = gHandlerService; let promise = TestUtils.topicObserved("handlersvc-json-replace-complete"); Services.obs.notifyObservers(null, "handlersvc-json-replace"); yield promise; }); + +/** + * Unloads the data store and deletes it. + */ +let deleteHandlerStore = Task.async(function* () { + yield unloadHandlerStore(); + + yield OS.File.remove(jsonPath, { ignoreAbsent: true }); +}); + +/** + * Unloads the data store and replaces it with the test data file. + */ +let copyTestDataToHandlerStore = Task.async(function* () { + yield unloadHandlerStore(); + + yield OS.File.copy(do_get_file("handlers.json").path, jsonPath); +}); + +var scriptFile = do_get_file("common_test_handlerService.js"); +Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec); + +/** + * Ensures forward compatibility by checking that the "store" method preserves + * unknown properties in the test data. This is specific to the JSON back-end. + */ +add_task(function* test_store_keeps_unknown_properties() { + // Create a new nsIHandlerInfo instance before loading the test data. + yield deleteHandlerStore(); + let handlerInfo = + HandlerServiceTestUtils.getHandlerInfo("example/type.handleinternally"); + + yield copyTestDataToHandlerStore(); + gHandlerService.store(handlerInfo); + + yield unloadHandlerStore(); + let data = JSON.parse(new TextDecoder().decode(yield OS.File.read(jsonPath))); + do_check_eq(data.mimeTypes["example/type.handleinternally"].unknownProperty, + "preserved"); +}); diff --git a/uriloader/exthandler/tests/unit/test_handlerService_rdf.js b/uriloader/exthandler/tests/unit/test_handlerService_rdf.js index f6205aadee61..1150d76ea937 100644 --- a/uriloader/exthandler/tests/unit/test_handlerService_rdf.js +++ b/uriloader/exthandler/tests/unit/test_handlerService_rdf.js @@ -1,37 +1,48 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -/** - * Tests the handlerService interfaces using RDF backend. +/* + * Tests the nsIHandlerService interface using the JSON backend. */ XPCOMUtils.defineLazyServiceGetter(this, "gHandlerService", "@mozilla.org/uriloader/handler-service;1", "nsIHandlerService"); -var scriptFile = do_get_file("common_test_handlerService.js"); -Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec); +/** + * 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 = Task.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; -var prepareImportDB = Task.async(function* () { - yield reloadData(); + let promise = TestUtils.topicObserved("handlersvc-rdf-replace-complete"); + Services.obs.notifyObservers(null, "handlersvc-rdf-replace"); + yield promise; +}); + +/** + * Unloads the data store and deletes it. + */ +let deleteHandlerStore = Task.async(function* () { + yield unloadHandlerStore(); + + yield OS.File.remove(rdfFile.path, { ignoreAbsent: true }); +}); + +/** + * Unloads the data store and replaces it with the test data file. + */ +let copyTestDataToHandlerStore = Task.async(function* () { + yield unloadHandlerStore(); let fileName = AppConstants.platform == "android" ? "mimeTypes-android.rdf" : "mimeTypes.rdf"; yield OS.File.copy(do_get_file(fileName).path, rdfFile.path); }); -var removeImportDB = Task.async(function* () { - yield reloadData(); - - yield OS.File.remove(rdfFile.path, { ignoreAbsent: true }); -}); - -var reloadData = Task.async(function* () { - // Force the initialization of handlerService to prevent observer is not initialized yet. - let svc = gHandlerService; - let promise = TestUtils.topicObserved("handlersvc-rdf-replace-complete"); - Services.obs.notifyObservers(null, "handlersvc-rdf-replace"); - yield promise; -}); +var scriptFile = do_get_file("common_test_handlerService.js"); +Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec);