Bug 1355585 - Streamline the format of "handlers.json", align the implementation, and reorganize tests. r=mak
☠☠ backed out by f1a900c8483d ☠ ☠
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Mon, 24 Apr 2017 10:31:30 +0100
changeset 402723 bcf5a28b1873a470c8626aeabae23ff69a191146
parent 402722 8bd40784fb5ba47389a8f6aa064a8f3ea590d48d
child 402724 11fa5271d39828a37777b80b95345f29689d3013
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1355585
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
uriloader/exthandler/nsHandlerService-json.js
uriloader/exthandler/nsHandlerService.js
uriloader/exthandler/tests/HandlerServiceTestUtils.jsm
uriloader/exthandler/tests/unit/common_test_handlerService.js
uriloader/exthandler/tests/unit/handlers.json
uriloader/exthandler/tests/unit/head.js
uriloader/exthandler/tests/unit/mimeTypes-android.rdf
uriloader/exthandler/tests/unit/mimeTypes.rdf
uriloader/exthandler/tests/unit/test_handlerService_json.js
uriloader/exthandler/tests/unit/test_handlerService_rdf.js
--- a/uriloader/exthandler/nsHandlerService-json.js
+++ b/uriloader/exthandler/nsHandlerService-json.js
@@ -34,42 +34,46 @@ HandlerService.prototype = {
     Ci.nsIHandlerService,
     Ci.nsIObserver
   ]),
 
   __store: null,
   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();
       this._updateDB();
     }
     return this.__store;
   },
 
   _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);
     }
   },
 
   _injectNewDefaults() {
     let schemesPrefBranch = Services.prefs.getBranch("gecko.handlerService.schemes.");
@@ -116,36 +120,25 @@ HandlerService.prototype = {
                                                             osDefaultHandlerFound.value);
       }
 
       // cache the possible handlers to avoid extra xpconnect traversals.
       let possibleHandlers = protoInfo.possibleApplicationHandlers;
 
       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) {
         yield this.__store.finalize();
       }
       this.__store = null;
     }.bind(this)).catch(Cu.reportError);
   },
@@ -158,119 +151,133 @@ HandlerService.prototype = {
     let promise = this._onDBChange();
     promise.then(() => {
       Services.obs.notifyObservers(null, "handlersvc-json-replace-complete");
     });
   },
 
   // 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);
     }
     for (let type of Object.keys(this._store.data.schemes)) {
       let handler = gExternalProtocolService.getProtocolHandlerInfo(type);
       handlers.appendElement(handler);
     }
     return handlers.enumerate();
   },
 
   // nsIHandlerService
   store(handlerInfo) {
-    let handlerObj = {
-      action: handlerInfo.preferredAction,
-      askBeforeHandling: handlerInfo.alwaysAskBeforeHandling,
-    };
+    let handlerList = this._getHandlerListByHandlerInfoType(handlerInfo);
+
+    // 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;
+    }
 
-    let preferredHandler = handlerInfo.preferredApplicationHandler;
-    if (preferredHandler) {
-      let serializable = this.handlerAppToSerializable(preferredHandler);
-      if (serializable) {
-        handlerObj.preferredHandler = serializable;
+    // 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();
   },
 
   // nsIHandlerService
   fillHandlerInfo(handlerInfo, overrideType) {
     let type = overrideType || handlerInfo.type;
     let storedHandlerInfo = this._getHandlerListByHandlerInfoType(handlerInfo)[type];
     if (!storedHandlerInfo) {
       throw new Components.Exception("handlerSvc fillHandlerInfo: don't know this type",
                                      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);
       }
     }
   },
 
   /**
    * @param handler
    *        A nsIHandlerApp handler app
    * @returns  Serializable representation of a handler app object.
@@ -308,47 +315,47 @@ HandlerService.prototype = {
   handlerAppFromSerializable(handlerObj) {
     let handlerApp;
     if ("path" in handlerObj) {
       try {
         let file = new FileUtils.File(handlerObj.path);
         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;
       handlerApp.dBusInterface = handlerObj.dBusInterface;
     } else {
       return null;
     }
 
     handlerApp.name = handlerObj.name;
     return handlerApp;
   },
 
   /**
-   * 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;
   },
 
   /**
    * Determines whether an nsIHandlerInfo instance represents a MIME type.
    */
   _isMIMEInfo(handlerInfo) {
     // We cannot rely only on the instanceof check because on Android both MIME
@@ -367,21 +374,20 @@ HandlerService.prototype = {
   remove(handlerInfo) {
     delete this._getHandlerListByHandlerInfoType(handlerInfo)[handlerInfo.type];
     this._store.saveSoon();
   },
 
   // 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]);
--- a/uriloader/exthandler/nsHandlerService.js
+++ b/uriloader/exthandler/nsHandlerService.js
@@ -704,37 +704,33 @@ HandlerService.prototype = {
 
   _storePreferredHandler: function HS__storePreferredHandler(aHandlerInfo) {
     var infoID = this._getInfoID(this._getClass(aHandlerInfo), aHandlerInfo.type);
     var handlerID =
       this._getPreferredHandlerID(this._getClass(aHandlerInfo), aHandlerInfo.type);
 
     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.
       //
       // Note: nsExternalHelperAppService::FillContentHandlerProperties ignores
       // this setting and instead identifies the preferred app as the resource
       // whose URI follows the pattern urn:<class>:externalApplication:<type>.
       // But the old downloadactions.js code used to set this property, so just
       // in case there is still some code somewhere that relies on its presence,
       // we set it here.
       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);
     }
   },
 
   /**
    * Store the list of possible handler apps for the content type represented
    * by the given handler info object.
--- a/uriloader/exthandler/tests/HandlerServiceTestUtils.jsm
+++ b/uriloader/exthandler/tests/HandlerServiceTestUtils.jsm
@@ -90,16 +90,24 @@ this.HandlerServiceTestUtils = {
    */
   getHandlerInfo(type) {
     if (type.includes("/")) {
       // We have to use the getFromTypeAndExtension method because we don't have
       // access to getMIMEInfoFromOS. This means that we have to reset the data
       // 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)) {
         this.handlerService.fillHandlerInfo(handlerInfo, "");
       }
       return handlerInfo;
     }
 
@@ -168,19 +176,22 @@ this.HandlerServiceTestUtils = {
   /**
    * Checks whether an nsIHandlerInfo instance matches the provided object.
    */
   assertHandlerInfoMatches(handlerInfo, expected) {
     let expectedInterface = expected.type.includes("/") ? Ci.nsIMIMEInfo
                                                         : 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();
       for (let expectedFileExtension of expected.fileExtensions || []) {
         Assert.equal(fileExtensionsEnumerator.getNext(), expectedFileExtension);
       }
       Assert.ok(!fileExtensionsEnumerator.hasMore());
     }
--- a/uriloader/exthandler/tests/unit/common_test_handlerService.js
+++ b/uriloader/exthandler/tests/unit/common_test_handlerService.js
@@ -1,538 +1,611 @@
-/* -*- 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");
+// 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 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 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 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;
-  }
-};
+/**
+ * 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;
+}
 
-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;
-  }
-};
-
-// 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
   // will be merged. The current implementation adds each saved file extension
   // 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: "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: "ircs",
-    preferredAction: Ci.nsIHandlerInfo.useHelperApp,
-    alwaysAskBeforeHandling: true,
+    type: "examplescheme.usesystemdefault",
+    preferredAction: Ci.nsIHandlerInfo.useSystemDefault,
+    alwaysAskBeforeHandling: false,
     possibleApplicationHandlers: [{
-      name: "Mibbit",
-      uriTemplate: "https://www.mibbit.com/?url=%s",
+      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",
     },{
       name: "Gmail",
       uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
     }],
   });
 
   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.");
+/**
+ * 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("This platform or locale does not have default handlers.");
     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);
+  // This will inject the default protocol handlers for the current locale.
+  yield deleteHandlerStore();
 
-  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],
-  });
+  yield assertAllHandlerInfosMatchDefaultHandlers();
 });
 
-// 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 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("Skipping test: No pref gecko.handlerService.defaultHandlersVersion.");
-    return;
-  }
-
-  yield removeImportDB();
-
-  let protoInfo = HandlerServiceTestUtils.getBlankHandlerInfo("nonexistent");
-  do_check_eq(protoInfo.possibleApplicationHandlers.length, 0);
-  gHandlerService.fillHandlerInfo(protoInfo, "ircs");
-  do_check_eq(protoInfo.possibleApplicationHandlers.length, 1);
-
-  // Remove the handler of irc first
-  let ircInfo = HandlerServiceTestUtils.getHandlerInfo("irc");
-  gHandlerService.remove(ircInfo);
-  do_check_false(gHandlerService.exists(ircInfo));
-
-  let origPrefs = 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);
-
-  // do reloading
-  yield reloadData();
-
-  // check "irc" exists again to make sure that injection actually happened
-  do_check_true(gHandlerService.exists(ircInfo));
-
-  // 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);
-
-  // 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.");
+    do_print("This platform or locale does not have default handlers.");
     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);
+  // This will inject the default protocol handlers for the current locale.
+  yield deleteHandlerStore();
 
-  yield reloadData();
-  do_check_true(gHandlerService.exists(protoInfo2));
+  // Remove the "irc" handler so we can verify that the injection is repeated.
+  let ircHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("irc");
+  gHandlerService.remove(ircHandlerInfo);
 
-  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);
+  let originalDefaultHandlersVersion = Services.prefs.getComplexValue(
+    "gecko.handlerService.defaultHandlersVersion", Ci.nsIPrefLocalizedString);
 
-  // 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);
+  // Set the preference to an arbitrarily high number to force injecting again.
+  Services.prefs.setStringPref("gecko.handlerService.defaultHandlersVersion",
+                               "999");
+
+  yield unloadHandlerStore();
 
-  // 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);
-  }
+  // Check that "irc" exists to make sure that the injection was repeated.
+  do_check_true(gHandlerService.exists(ircHandlerInfo));
+
+  // There should be no duplicate handlers in the protocols.
+  yield assertAllHandlerInfosMatchDefaultHandlers();
+
+  Services.prefs.setStringPref("gecko.handlerService.defaultHandlersVersion",
+                               originalDefaultHandlersVersion);
 });
-
-// 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);
-  }
-});
--- a/uriloader/exthandler/tests/unit/handlers.json
+++ b/uriloader/exthandler/tests/unit/handlers.json
@@ -1,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"
+        }
+      ]
+    }
+  }
+}
--- a/uriloader/exthandler/tests/unit/head.js
+++ b/uriloader/exthandler/tests/unit/head.js
@@ -6,33 +6,33 @@
  * Initialization for tests related to invoking external handler applications.
  */
 
 "use strict";
 
 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");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 Cu.import("resource://testing-common/HandlerServiceTestUtils.jsm", this);
 Cu.import("resource://testing-common/TestUtils.jsm");
 
 HandlerServiceTestUtils.Assert = Assert;
 
 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()) {
     rdfFile.remove(false);
   }
 }
 
 // Delete the existing datasource file, if any, so we start from scratch.
--- a/uriloader/exthandler/tests/unit/mimeTypes-android.rdf
+++ b/uriloader/exthandler/tests/unit/mimeTypes-android.rdf
@@ -1,78 +1,100 @@
 <?xml version="1.0"?>
 <RDF:RDF xmlns:NC="http://home.netscape.com/NC-rdf#"
          xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-  <RDF:Description RDF:about="urn:mimetype:handler:webcal"
-                   NC:alwaysAsk="true">
-    <NC:externalApplication RDF:resource="urn:mimetype:externalApplication:webcal"/>
-    <NC:possibleApplication RDF:resource="urn:handler:web:http://30boxes.com/external/widget?refer=ff&amp;url=%s"/>
-    <NC:possibleApplication RDF:resource="urn:handler:web:https://30boxes.com/external/widget?refer=ff&amp;url=%s"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:mimetype:nonexistent/type"
-                   NC:value="nonexistent/type"
-                   NC:fileExtensions="pdf">
-    <NC:handlerProp RDF:resource="urn:mimetype:handler:nonexistent/type"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:mimetype:ircs"
-                   NC:value="ircs">
-    <NC:handlerProp RDF:resource="urn:mimetype:handler:ircs"/>
+  <RDF:Seq RDF:about="urn:mimetypes:root">
+    <RDF:li RDF:resource="urn:mimetype:example/type.handleinternally"/>
+    <RDF:li RDF:resource="urn:mimetype:example/type.savetodisk"/>
+    <RDF:li RDF:resource="urn:mimetype:example/type.usehelperapp"/>
+    <RDF:li RDF:resource="urn:mimetype:example/type.usesystemdefault"/>
+    <RDF:li RDF:resource="urn:mimetype:examplescheme.usehelperapp"/>
+    <RDF:li RDF:resource="urn:mimetype:examplescheme.usesystemdefault"/>
+  </RDF:Seq>
+  <RDF:Description RDF:about="urn:mimetype:example/type.usehelperapp"
+                   NC:value="example/type.usehelperapp">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.usehelperapp"/>
+    <NC:fileExtensions>example_two</NC:fileExtensions>
+    <NC:fileExtensions>example_three</NC:fileExtensions>
   </RDF:Description>
-  <RDF:Description RDF:about="urn:handler:web:https://compose.mail.yahoo.com/?To=%s"
-                   NC:prettyName="Yahoo! Mail"
-                   NC:uriTemplate="https://compose.mail.yahoo.com/?To=%s" />
-  <RDF:Description RDF:about="urn:handler:web:https://30boxes.com/external/widget?refer=ff&amp;url=%s"
-                   NC:prettyName="30 Boxes"
-                   NC:uriTemplate="https://30boxes.com/external/widget?refer=ff&amp;url=%s" />
-  <RDF:Seq RDF:about="urn:mimetypes:root">
-    <RDF:li RDF:resource="urn:mimetype:irc"/>
-    <RDF:li RDF:resource="urn:mimetype:ircs"/>
-    <RDF:li RDF:resource="urn:mimetype:mailto"/>
-    <RDF:li RDF:resource="urn:mimetype:nonexistent/type"/>
-    <RDF:li RDF:resource="urn:mimetype:webcal"/>
-  </RDF:Seq>
-  <RDF:Description RDF:about="urn:mimetype:handler:nonexistent/type"
+  <RDF:Description RDF:about="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"
+                   NC:prettyName="Example Possible Handler Two"
+                   NC:uriTemplate="http://www.example.com/?id=2&amp;url=%s" />
+  <RDF:Description RDF:about="urn:mimetype:handler:example/type.handleinternally"
                    NC:handleInternal="true"
                    NC:alwaysAsk="false" />
-  <RDF:Description RDF:about="urn:handler:web:https://www.mibbit.com/?url=%s"
-                   NC:prettyName="Mibbit"
-                   NC:uriTemplate="https://www.mibbit.com/?url=%s" />
-  <RDF:Description RDF:about="urn:mimetype:externalApplication:webcal"
-                   NC:prettyName="30 Boxes"
-                   NC:uriTemplate="http://30boxes.com/external/widget?refer=ff&amp;url=%s" />
-  <RDF:Description RDF:about="urn:mimetype:mailto"
-                   NC:value="mailto">
-    <NC:handlerProp RDF:resource="urn:mimetype:handler:mailto"/>
+  <RDF:Description RDF:about="urn:mimetype:examplescheme.usesystemdefault"
+                   NC:value="examplescheme.usesystemdefault">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:examplescheme.usesystemdefault"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:mimetype:externalApplication:example/type.usehelperapp"
+                   NC:prettyName="Example Default Handler"
+                   NC:uriTemplate="https://www.example.com/?url=%s" />
+  <RDF:Description RDF:about="urn:mimetype:externalApplication:example/type.savetodisk"
+                   NC:prettyName="Example Default Handler"
+                   NC:uriTemplate="https://www.example.com/?url=%s" />
+  <RDF:Description RDF:about="urn:mimetype:example/type.usesystemdefault"
+                   NC:value="example/type.usesystemdefault">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.usesystemdefault"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:mimetype:examplescheme.usehelperapp"
+                   NC:value="examplescheme.usehelperapp">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:examplescheme.usehelperapp"/>
   </RDF:Description>
-  <RDF:Description RDF:about="urn:mimetype:handler:mailto"
+  <RDF:Description RDF:about="urn:handler:web:http://www.example.com/?url=%s"
+                   NC:prettyName="Example Possible Handler"
+                   NC:uriTemplate="http://www.example.com/?url=%s" />
+  <RDF:Description RDF:about="urn:mimetype:example/type.savetodisk"
+                   NC:value="example/type.savetodisk">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.savetodisk"/>
+    <NC:fileExtensions>example_two</NC:fileExtensions>
+    <NC:fileExtensions>example_three</NC:fileExtensions>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:mimetype:handler:examplescheme.usehelperapp"
+                   NC:alwaysAsk="true">
+    <NC:externalApplication RDF:resource="urn:mimetype:externalApplication:examplescheme.usehelperapp"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:mimetype:example/type.handleinternally"
+                   NC:value="example/type.handleinternally"
+                   NC:fileExtensions="example_one">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.handleinternally"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"
+                   NC:prettyName="Example Possible Handler One"
+                   NC:uriTemplate="http://www.example.com/?id=1&amp;url=%s" />
+  <RDF:Description RDF:about="urn:handler:web:https://www.example.com/?url=%s"
+                   NC:prettyName="Example Default Handler"
+                   NC:uriTemplate="https://www.example.com/?url=%s" />
+  <RDF:Description RDF:about="urn:mimetype:handler:examplescheme.usesystemdefault"
                    NC:useSystemDefault="true"
                    NC:alwaysAsk="false">
-    <NC:possibleApplication RDF:resource="urn:handler:web:https://compose.mail.yahoo.com/?To=%s"/>
-    <NC:possibleApplication RDF:resource="urn:handler:web:https://mail.google.com/mail/?extsrc=mailto&amp;url=%s"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:mimetype:handler:ircs"
-                   NC:alwaysAsk="true">
-    <NC:possibleApplication RDF:resource="urn:handler:web:https://www.mibbit.com/?url=%s"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?url=%s"/>
   </RDF:Description>
-  <RDF:Description RDF:about="urn:handler:web:https://mail.google.com/mail/?extsrc=mailto&amp;url=%s"
-                   NC:prettyName="Gmail"
-                   NC:uriTemplate="https://mail.google.com/mail/?extsrc=mailto&amp;url=%s" />
-  <RDF:Description RDF:about="urn:mimetype:webcal"
-                   NC:value="webcal">
-    <NC:handlerProp RDF:resource="urn:mimetype:handler:webcal"/>
+  <RDF:Description RDF:about="urn:mimetype:handler:example/type.usesystemdefault"
+                   NC:useSystemDefault="true"
+                   NC:alwaysAsk="false">
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?url=%s"/>
   </RDF:Description>
-  <RDF:Description RDF:about="urn:handler:web:http://30boxes.com/external/widget?refer=ff&amp;url=%s"
-                   NC:prettyName="30 Boxes"
-                   NC:uriTemplate="http://30boxes.com/external/widget?refer=ff&amp;url=%s" />
   <RDF:Description RDF:about="urn:root"
                    NC:en-US_defaultHandlersVersion="999" />
-  <RDF:Description RDF:about="urn:mimetype:handler:irc"
+  <RDF:Description RDF:about="urn:mimetype:handler:example/type.usehelperapp"
                    NC:alwaysAsk="true">
-    <NC:possibleApplication RDF:resource="urn:handler:web:https://www.mibbit.com/?url=%s"/>
+    <NC:externalApplication RDF:resource="urn:mimetype:externalApplication:example/type.usehelperapp"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"/>
   </RDF:Description>
-  <RDF:Description RDF:about="urn:mimetype:irc"
-                   NC:value="irc">
-    <NC:handlerProp RDF:resource="urn:mimetype:handler:irc"/>
-  </RDF:Description>
+  <RDF:Description RDF:about="urn:mimetype:externalApplication:examplescheme.usehelperapp"
+                   NC:prettyName="Example Default Handler"
+                   NC:uriTemplate="https://www.example.com/?url=%s" />
   <RDF:Description RDF:about="urn:mimetypes">
     <NC:MIME-types RDF:resource="urn:mimetypes:root"/>
   </RDF:Description>
+  <RDF:Description RDF:about="urn:mimetype:handler:example/type.savetodisk"
+                   NC:saveToDisk="true"
+                   NC:alwaysAsk="true">
+    <NC:externalApplication RDF:resource="urn:mimetype:externalApplication:example/type.savetodisk"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
+  </RDF:Description>
 </RDF:RDF>
--- a/uriloader/exthandler/tests/unit/mimeTypes.rdf
+++ b/uriloader/exthandler/tests/unit/mimeTypes.rdf
@@ -1,83 +1,105 @@
 <?xml version="1.0"?>
 <RDF:RDF xmlns:NC="http://home.netscape.com/NC-rdf#"
          xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+  <RDF:Description RDF:about="urn:mimetype:externalApplication:example/type.usehelperapp"
+                   NC:prettyName="Example Default Handler"
+                   NC:uriTemplate="https://www.example.com/?url=%s" />
+  <RDF:Description RDF:about="urn:scheme:examplescheme.usesystemdefault"
+                   NC:value="examplescheme.usesystemdefault">
+    <NC:handlerProp RDF:resource="urn:scheme:handler:examplescheme.usesystemdefault"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:scheme:handler:examplescheme.usehelperapp"
+                   NC:alwaysAsk="true">
+    <NC:externalApplication RDF:resource="urn:scheme:externalApplication:examplescheme.usehelperapp"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:handler:web:http://www.example.com/?url=%s"
+                   NC:prettyName="Example Possible Handler"
+                   NC:uriTemplate="http://www.example.com/?url=%s" />
+  <RDF:Description RDF:about="urn:scheme:handler:examplescheme.usesystemdefault"
+                   NC:useSystemDefault="true"
+                   NC:alwaysAsk="false">
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?url=%s"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:mimetype:example/type.savetodisk"
+                   NC:value="example/type.savetodisk">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.savetodisk"/>
+    <NC:fileExtensions>example_two</NC:fileExtensions>
+    <NC:fileExtensions>example_three</NC:fileExtensions>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:mimetype:example/type.usesystemdefault"
+                   NC:value="example/type.usesystemdefault">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.usesystemdefault"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:mimetype:handler:example/type.savetodisk"
+                   NC:saveToDisk="true"
+                   NC:alwaysAsk="true">
+    <NC:externalApplication RDF:resource="urn:mimetype:externalApplication:example/type.savetodisk"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"
+                   NC:prettyName="Example Possible Handler One"
+                   NC:uriTemplate="http://www.example.com/?id=1&amp;url=%s" />
+  <RDF:Description RDF:about="urn:mimetype:example/type.handleinternally"
+                   NC:value="example/type.handleinternally"
+                   NC:fileExtensions="example_one">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.handleinternally"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:root"
+                   NC:en-US_defaultHandlersVersion="999" />
+  <RDF:Description RDF:about="urn:mimetype:handler:example/type.usesystemdefault"
+                   NC:useSystemDefault="true"
+                   NC:alwaysAsk="false">
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?url=%s"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:scheme:externalApplication:examplescheme.usehelperapp"
+                   NC:prettyName="Example Default Handler"
+                   NC:uriTemplate="https://www.example.com/?url=%s" />
+  <RDF:Seq RDF:about="urn:mimetypes:root">
+    <RDF:li RDF:resource="urn:mimetype:example/type.handleinternally"/>
+    <RDF:li RDF:resource="urn:mimetype:example/type.savetodisk"/>
+    <RDF:li RDF:resource="urn:mimetype:example/type.usehelperapp"/>
+    <RDF:li RDF:resource="urn:mimetype:example/type.usesystemdefault"/>
+  </RDF:Seq>
+  <RDF:Description RDF:about="urn:mimetype:handler:example/type.usehelperapp"
+                   NC:alwaysAsk="true">
+    <NC:externalApplication RDF:resource="urn:mimetype:externalApplication:example/type.usehelperapp"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"/>
+    <NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:scheme:examplescheme.usehelperapp"
+                   NC:value="examplescheme.usehelperapp">
+    <NC:handlerProp RDF:resource="urn:scheme:handler:examplescheme.usehelperapp"/>
+  </RDF:Description>
+  <RDF:Seq RDF:about="urn:schemes:root">
+    <RDF:li RDF:resource="urn:scheme:examplescheme.usehelperapp"/>
+    <RDF:li RDF:resource="urn:scheme:examplescheme.usesystemdefault"/>
+  </RDF:Seq>
+  <RDF:Description RDF:about="urn:handler:web:https://www.example.com/?url=%s"
+                   NC:prettyName="Example Default Handler"
+                   NC:uriTemplate="https://www.example.com/?url=%s" />
+  <RDF:Description RDF:about="urn:mimetype:externalApplication:example/type.savetodisk"
+                   NC:prettyName="Example Default Handler"
+                   NC:uriTemplate="https://www.example.com/?url=%s" />
+  <RDF:Description RDF:about="urn:mimetypes">
+    <NC:MIME-types RDF:resource="urn:mimetypes:root"/>
+  </RDF:Description>
+  <RDF:Description RDF:about="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"
+                   NC:prettyName="Example Possible Handler Two"
+                   NC:uriTemplate="http://www.example.com/?id=2&amp;url=%s" />
   <RDF:Description RDF:about="urn:schemes">
     <NC:Protocol-Schemes RDF:resource="urn:schemes:root"/>
   </RDF:Description>
-  <RDF:Seq RDF:about="urn:mimetypes:root">
-    <RDF:li RDF:resource="urn:mimetype:nonexistent/type"/>
-  </RDF:Seq>
-  <RDF:Description RDF:about="urn:mimetype:handler:nonexistent/type"
+  <RDF:Description RDF:about="urn:mimetype:handler:example/type.handleinternally"
                    NC:handleInternal="true"
                    NC:alwaysAsk="false" />
-  <RDF:Description RDF:about="urn:scheme:irc"
-                   NC:value="irc">
-    <NC:handlerProp RDF:resource="urn:scheme:handler:irc"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:scheme:externalApplication:webcal"
-                   NC:prettyName="30 Boxes"
-                   NC:uriTemplate="http://30boxes.com/external/widget?refer=ff&amp;url=%s" />
-  <RDF:Description RDF:about="urn:scheme:handler:ircs"
-                   NC:alwaysAsk="true">
-    <NC:possibleApplication RDF:resource="urn:handler:web:https://www.mibbit.com/?url=%s"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:scheme:handler:mailto"
-                   NC:useSystemDefault="true"
-                   NC:alwaysAsk="false">
-    <NC:possibleApplication RDF:resource="urn:handler:web:https://compose.mail.yahoo.com/?To=%s"/>
-    <NC:possibleApplication RDF:resource="urn:handler:web:https://mail.google.com/mail/?extsrc=mailto&amp;url=%s"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:scheme:ircs"
-                   NC:value="ircs">
-    <NC:handlerProp RDF:resource="urn:scheme:handler:ircs"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:handler:web:https://www.mibbit.com/?url=%s"
-                   NC:prettyName="Mibbit"
-                   NC:uriTemplate="https://www.mibbit.com/?url=%s" />
-  <RDF:Description RDF:about="urn:mimetype:nonexistent/type"
-                   NC:value="nonexistent/type"
-                   NC:fileExtensions="pdf">
-    <NC:handlerProp RDF:resource="urn:mimetype:handler:nonexistent/type"/>
+  <RDF:Description RDF:about="urn:mimetype:example/type.usehelperapp"
+                   NC:value="example/type.usehelperapp">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.usehelperapp"/>
+    <NC:fileExtensions>example_two</NC:fileExtensions>
+    <NC:fileExtensions>example_three</NC:fileExtensions>
   </RDF:Description>
-  <RDF:Description RDF:about="urn:handler:web:https://compose.mail.yahoo.com/?To=%s"
-                   NC:prettyName="Yahoo! Mail"
-                   NC:uriTemplate="https://compose.mail.yahoo.com/?To=%s" />
-  <RDF:Description RDF:about="urn:handler:web:https://mail.google.com/mail/?extsrc=mailto&amp;url=%s"
-                   NC:prettyName="Gmail"
-                   NC:uriTemplate="https://mail.google.com/mail/?extsrc=mailto&amp;url=%s" />
-  <RDF:Description RDF:about="urn:scheme:webcal"
-                   NC:value="webcal">
-    <NC:handlerProp RDF:resource="urn:scheme:handler:webcal"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:root"
-                   NC:en-US_defaultHandlersVersion="999" />
-  <RDF:Description RDF:about="urn:scheme:mailto"
-                   NC:value="mailto">
-    <NC:handlerProp RDF:resource="urn:scheme:handler:mailto"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:scheme:handler:irc"
-                   NC:alwaysAsk="true">
-    <NC:possibleApplication RDF:resource="urn:handler:web:https://www.mibbit.com/?url=%s"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:scheme:handler:webcal"
-                   NC:alwaysAsk="true">
-    <NC:externalApplication RDF:resource="urn:scheme:externalApplication:webcal"/>
-    <NC:possibleApplication RDF:resource="urn:handler:web:http://30boxes.com/external/widget?refer=ff&amp;url=%s"/>
-    <NC:possibleApplication RDF:resource="urn:handler:web:https://30boxes.com/external/widget?refer=ff&amp;url=%s"/>
-  </RDF:Description>
-  <RDF:Seq RDF:about="urn:schemes:root">
-    <RDF:li RDF:resource="urn:scheme:irc"/>
-    <RDF:li RDF:resource="urn:scheme:ircs"/>
-    <RDF:li RDF:resource="urn:scheme:mailto"/>
-    <RDF:li RDF:resource="urn:scheme:webcal"/>
-  </RDF:Seq>
-  <RDF:Description RDF:about="urn:mimetypes">
-    <NC:MIME-types RDF:resource="urn:mimetypes:root"/>
-  </RDF:Description>
-  <RDF:Description RDF:about="urn:handler:web:http://30boxes.com/external/widget?refer=ff&amp;url=%s"
-                   NC:prettyName="30 Boxes"
-                   NC:uriTemplate="http://30boxes.com/external/widget?refer=ff&amp;url=%s" />
-  <RDF:Description RDF:about="urn:handler:web:https://30boxes.com/external/widget?refer=ff&amp;url=%s"
-                   NC:prettyName="30 Boxes"
-                   NC:uriTemplate="https://30boxes.com/external/widget?refer=ff&amp;url=%s" />
 </RDF:RDF>
--- 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;
+
+  let promise = TestUtils.topicObserved("handlersvc-json-replace-complete");
+  Services.obs.notifyObservers(null, "handlersvc-json-replace");
+  yield promise;
+});
 
-var prepareImportDB = Task.async(function* () {
-  yield reloadData();
+/**
+ * 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 removeImportDB = Task.async(function* () {
-  yield reloadData();
-
-  yield OS.File.remove(jsonPath, { ignoreAbsent: true });
-});
+var scriptFile = do_get_file("common_test_handlerService.js");
+Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec);
 
-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;
+/**
+ * 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");
 });
--- 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;
+
+  let promise = TestUtils.topicObserved("handlersvc-rdf-replace-complete");
+  Services.obs.notifyObservers(null, "handlersvc-rdf-replace");
+  yield promise;
+});
 
-var prepareImportDB = Task.async(function* () {
-  yield reloadData();
+/**
+ * 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);