Bug 1287660 - New implementation of nsIHandlerService for the JSON backend. r=paolo draft
authorAlphan Chen <alchen@mozilla.com>
Fri, 10 Mar 2017 17:41:33 +0800
changeset 496558 62665abe3bd5e262bd99085930c45a41e4981bf1
parent 496313 35398cae65c1526ce45c23a5f8b5568c5ada4762
child 548641 2f63964ffca28de4955795810deb6b055eefa8b7
push id48629
push useralchen@mozilla.com
push dateFri, 10 Mar 2017 09:42:02 +0000
reviewerspaolo
bugs1287660
milestone55.0a1
Bug 1287660 - New implementation of nsIHandlerService for the JSON backend. r=paolo MozReview-Commit-ID: 46fQq1Pr8bT
browser/installer/package-manifest.in
mobile/android/installer/package-manifest.in
uriloader/exthandler/moz.build
uriloader/exthandler/nsHandlerService-json.js
uriloader/exthandler/nsHandlerService-json.manifest
uriloader/exthandler/nsHandlerService.js
uriloader/exthandler/tests/unit/common_test_handlerService.js
uriloader/exthandler/tests/unit/handlers.json
uriloader/exthandler/tests/unit/head_handlerService.js
uriloader/exthandler/tests/unit/mimeTypes.rdf
uriloader/exthandler/tests/unit/test_handlerService_json.js
uriloader/exthandler/tests/unit/test_handlerService_rdf.js
uriloader/exthandler/tests/unit/xpcshell.ini
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -454,16 +454,18 @@
 @RESPATH@/components/mozProtocolHandler.js
 @RESPATH@/components/mozProtocolHandler.manifest
 @RESPATH@/components/nsDefaultCLH.manifest
 @RESPATH@/components/nsDefaultCLH.js
 @RESPATH@/components/nsContentPrefService.manifest
 @RESPATH@/components/nsContentPrefService.js
 @RESPATH@/components/nsContentDispatchChooser.manifest
 @RESPATH@/components/nsContentDispatchChooser.js
+@RESPATH@/components/nsHandlerService-json.manifest
+@RESPATH@/components/nsHandlerService-json.js
 @RESPATH@/components/nsHandlerService.manifest
 @RESPATH@/components/nsHandlerService.js
 @RESPATH@/components/nsWebHandlerApp.manifest
 @RESPATH@/components/nsWebHandlerApp.js
 @RESPATH@/components/satchel.manifest
 @RESPATH@/components/nsFormAutoComplete.js
 @RESPATH@/components/FormHistoryStartup.js
 @RESPATH@/components/nsInputListAutoComplete.js
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -325,16 +325,18 @@
 @BINPATH@/components/MainProcessSingleton.js
 @BINPATH@/components/ContentProcessSingleton.js
 @BINPATH@/components/nsURLFormatter.manifest
 @BINPATH@/components/nsURLFormatter.js
 @BINPATH@/components/txEXSLTRegExFunctions.manifest
 @BINPATH@/components/txEXSLTRegExFunctions.js
 @BINPATH@/components/nsContentPrefService.manifest
 @BINPATH@/components/nsContentPrefService.js
+@BINPATH@/components/nsHandlerService-json.manifest
+@BINPATH@/components/nsHandlerService-json.js
 @BINPATH@/components/nsHandlerService.manifest
 @BINPATH@/components/nsHandlerService.js
 @BINPATH@/components/nsWebHandlerApp.manifest
 @BINPATH@/components/nsWebHandlerApp.js
 @BINPATH@/components/satchel.manifest
 @BINPATH@/components/nsFormAutoComplete.js
 @BINPATH@/components/FormHistoryStartup.js
 @BINPATH@/components/nsInputListAutoComplete.js
--- a/uriloader/exthandler/moz.build
+++ b/uriloader/exthandler/moz.build
@@ -103,16 +103,18 @@ if CONFIG['MOZ_ENABLE_DBUS']:
     ]
 
 if CONFIG['MOZ_ENABLE_CONTENTACTION']:
     UNIFIED_SOURCES += [
         'nsContentHandlerApp.cpp',
     ]
 
 EXTRA_COMPONENTS += [
+    'nsHandlerService-json.js',
+    'nsHandlerService-json.manifest',
     'nsHandlerService.js',
     'nsHandlerService.manifest',
     'nsWebHandlerApp.js',
     'nsWebHandlerApp.manifest',
 ]
 
 IPDL_SOURCES += [
     'PExternalHelperApp.ipdl',
new file mode 100644
--- /dev/null
+++ b/uriloader/exthandler/nsHandlerService-json.js
@@ -0,0 +1,385 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
+                                  "resource://gre/modules/JSONFile.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gExternalProtocolService",
+                                   "@mozilla.org/uriloader/external-protocol-service;1",
+                                   "nsIExternalProtocolService");
+XPCOMUtils.defineLazyServiceGetter(this, "gMIMEService",
+                                   "@mozilla.org/mime;1",
+                                   "nsIMIMEService");
+
+function HandlerService() {
+  // Observe handlersvc-json-replace so we can switch to the datasource
+  Services.obs.addObserver(this, "handlersvc-json-replace", true);
+}
+
+HandlerService.prototype = {
+
+  classID: Components.ID("{220cc253-b60f-41f6-b9cf-fdcb325f970f}"),
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsISupportsWeakReference,
+    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"),
+        dataPostProcessor: this._dataPostProcessor.bind(this),
+      });
+      this.__store.ensureDataReady();
+      this._updateDB();
+    }
+    return this.__store;
+  },
+
+  _dataPostProcessor(data) {
+    return data.schemes ? data : { version: {}, mimetypes: {}, schemes: {} };
+  },
+
+  _updateDB() {
+    try {
+
+      let locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+                     .getService(Ci.nsIXULChromeRegistry)
+                     .getSelectedLocale("global");
+      let prefsDefaultHandlersVersion = Number(Services.prefs.getComplexValue(
+        "gecko.handlerService.defaultHandlersVersion",
+        Ci.nsIPrefLocalizedString).data);
+
+      let defaultHandlersVersion = this._store.data.version[locale] || 0;
+      if (defaultHandlersVersion < prefsDefaultHandlersVersion ) {
+        this._injectNewDefaults();
+        this._store.data.version[locale] = prefsDefaultHandlersVersion;
+      }
+    } catch (ex) {
+      Cu.reportError(ex);
+    }
+  },
+
+  _injectNewDefaults() {
+    let schemesPrefBranch = Services.prefs.getBranch("gecko.handlerService.schemes.");
+    let schemePrefList = schemesPrefBranch.getChildList("");
+
+    let schemes = {};
+
+    // read all the scheme prefs into a hash
+    for (let schemePrefName of schemePrefList) {
+
+      let [scheme, handlerNumber, attribute] = schemePrefName.split(".");
+
+      try {
+        let attrData =
+          schemesPrefBranch.getComplexValue(schemePrefName,
+                                            Ci.nsIPrefLocalizedString).data;
+        if (!(scheme in schemes)) {
+          schemes[scheme] = {};
+        }
+
+        if (!(handlerNumber in schemes[scheme])) {
+          schemes[scheme][handlerNumber] = {};
+        }
+
+        schemes[scheme][handlerNumber][attribute] = attrData;
+      } catch (ex) {}
+    }
+
+    for (let scheme of Object.keys(schemes)) {
+
+      // This clause is essentially a reimplementation of
+      // nsIExternalProtocolHandlerService.getProtocolHandlerInfo().
+      // Necessary because we want to use this instance of the service,
+      // but nsIExternalProtocolHandlerService would call the RDF-based based version
+      // until we complete the conversion.
+      let osDefaultHandlerFound = {};
+      let protoInfo = gExternalProtocolService.getProtocolHandlerInfoFromOS(scheme,
+                                                                            osDefaultHandlerFound);
+
+      if (this.exists(protoInfo)) {
+        this.fillHandlerInfo(protoInfo, null);
+      } else {
+        gExternalProtocolService.setProtocolHandlerDefaults(protoInfo,
+                                                            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, 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);
+  },
+
+  // nsIObserver
+  observe(subject, topic, data) {
+    if (topic != "handlersvc-json-replace") {
+      return;
+    }
+    let promise = this._onDBChange();
+    promise.then(() => {
+      Services.obs.notifyObservers(null, "handlersvc-json-replace-complete", null);
+    });
+  },
+
+  // nsIHandlerService
+  enumerate() {
+    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, false);
+    }
+    for (let type of Object.keys(this._store.data.schemes)) {
+      let handler = gExternalProtocolService.getProtocolHandlerInfo(type);
+      handlers.appendElement(handler, false);
+    }
+    return handlers.enumerate();
+  },
+
+  // nsIHandlerService
+  store(handlerInfo) {
+    let handlerObj = {
+      action: handlerInfo.preferredAction,
+      askBeforeHandling: handlerInfo.alwaysAskBeforeHandling,
+    };
+
+    if (handlerInfo.description) {
+      handlerObj.description = handlerInfo.description;
+    }
+
+    let preferredHandler = handlerInfo.preferredApplicationHandler;
+    if (preferredHandler) {
+      let serializable = this.handlerAppToSerializable(preferredHandler);
+      if (serializable) {
+        handlerObj.preferredHandler = serializable;
+      }
+    }
+
+    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 (possibleHandlers.length) {
+      handlerObj.possibleHandlers = possibleHandlers;
+    }
+
+    if (handlerInfo instanceof Ci.nsIMIMEInfo) {
+      let extEnumerator = handlerInfo.getFileExtensions();
+      let extensions = [];
+      while (extEnumerator.hasMore()) {
+        let extension = extEnumerator.getNext();
+        if (!extensions.includes(extension)) {
+          extensions.push(extension);
+        }
+      }
+      if (extensions.length) {
+        handlerObj.fileExtensions = 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);
+    }
+    handlerInfo.description = storedHandlerInfo.description;
+
+    // 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;
+    }
+
+    let preferHandler = null;
+    if (storedHandlerInfo.preferredHandler) {
+      preferHandler = this.handlerAppFromSerializable(storedHandlerInfo.preferredHandler);
+    }
+    handlerInfo.preferredApplicationHandler = preferHandler;
+    if (preferHandler) {
+      handlerInfo.possibleApplicationHandlers.appendElement(preferHandler, false);
+    }
+
+    if (storedHandlerInfo.possibleHandlers) {
+      for (let handler of storedHandlerInfo.possibleHandlers) {
+        let possibleHandler = this.handlerAppFromSerializable(handler);
+        if (possibleHandler && (!preferHandler ||
+                                !possibleHandler.equals(preferHandler))) {
+          handlerInfo.possibleApplicationHandlers.appendElement(possibleHandler, false);
+        }
+      }
+    }
+
+    // We always store "askBeforeHandling" in the JSON file. Just use this value.
+    handlerInfo.alwaysAskBeforeHandling = storedHandlerInfo.askBeforeHandling;
+
+    if (handlerInfo instanceof Ci.nsIMIMEInfo) {
+      if (storedHandlerInfo.fileExtensions) {
+        for (let extension of storedHandlerInfo.fileExtensions) {
+          handlerInfo.appendExtension(extension);
+        }
+      }
+    }
+  },
+
+  /**
+   * @param handler
+   *        A nsIHandlerApp handler app
+   * @returns  Serializable representation of a handler app object.
+   */
+  handlerAppToSerializable(handler) {
+    if (handler instanceof Ci.nsILocalHandlerApp) {
+      return {
+        name: handler.name,
+        path: handler.executable.path,
+      };
+    } else if (handler instanceof Ci.nsIWebHandlerApp) {
+      return {
+        name: handler.name,
+        uriTemplate: handler.uriTemplate,
+      };
+    } else if (handler instanceof Ci.nsIDBusHandlerApp) {
+      return {
+        name: handler.name,
+        service: handler.service,
+        method: handler.method,
+        objectPath: handler.objectPath,
+        dBusInterface: handler.dBusInterface,
+      };
+    }
+    // If the handler is an unknown handler type, return null.
+    // Android default application handler is the case.
+    return null;
+  },
+
+  /**
+   * @param handlerObj
+   *        Serializable representation of a handler object.
+   * @returns  {nsIHandlerApp}  the handler app, if any; otherwise null
+   */
+  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.executable = file;
+      } catch (ex) {
+        return null;
+      }
+    } else if ("uriTemplate" in handlerObj) {
+      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.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
+   * based on which type of handlerInfo is provided.
+   */
+  _getHandlerListByHandlerInfoType(handlerInfo) {
+    if (handlerInfo instanceof Ci.nsIMIMEInfo) {
+      return this._store.data.mimetypes;
+    }
+    return this._store.data.schemes;
+  },
+
+  // nsIHandlerService
+  exists(handlerInfo) {
+    return handlerInfo.type in this._getHandlerListByHandlerInfoType(handlerInfo);
+  },
+
+  // nsIHandlerService
+  remove(handlerInfo) {
+    delete this._getHandlerListByHandlerInfoType(handlerInfo)[handlerInfo.type];
+    this._store.saveSoon();
+  },
+
+  // nsIHandlerService
+  getTypeFromExtension(fileExtension) {
+    let extension = fileExtension.toLowerCase();
+    let mimeTypes = this._store.data.mimetypes;
+    for (let type of Object.keys(mimeTypes)) {
+      if (mimeTypes[type].fileExtensions &&
+          mimeTypes[type].fileExtensions.includes(extension)) {
+          return type;
+      }
+    }
+    return "";
+  },
+
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]);
new file mode 100644
--- /dev/null
+++ b/uriloader/exthandler/nsHandlerService-json.manifest
@@ -0,0 +1,2 @@
+component {220cc253-b60f-41f6-b9cf-fdcb325f970f} nsHandlerService-json.js
+contract @mozilla.org/uriloader/handler-service-json;1 {220cc253-b60f-41f6-b9cf-fdcb325f970f} process=main
--- a/uriloader/exthandler/nsHandlerService.js
+++ b/uriloader/exthandler/nsHandlerService.js
@@ -113,19 +113,19 @@ HandlerService.prototype = {
     this._observerSvc.addObserver(this, "profile-before-change", false);
 
     // Observe xpcom-shutdown so we can remove these observers
     // when the application shuts down.
     this._observerSvc.addObserver(this, "xpcom-shutdown", false);
 
     // Observe profile-do-change so that non-default profiles get upgraded too
     this._observerSvc.addObserver(this, "profile-do-change", false);
-    
-    // do any necessary updating of the datastore
-    this._updateDB();
+
+    // Observe handlersvc-rdf-replace so we can switch to the datasource
+    this._observerSvc.addObserver(this, "handlersvc-rdf-replace", false);
   },
 
   _updateDB: function HS__updateDB() {
     try {
       var defaultHandlersVersion = this._datastoreDefaultHandlersVersion;
     } catch(ex) {
       // accessing the datastore failed, we can't update anything
       return;
@@ -155,16 +155,17 @@ HandlerService.prototype = {
     var currentLocale = chromeRegistry.getSelectedLocale("global");
     return currentLocale;
   }, 
 
   _destroy: function HS__destroy() {
     this._observerSvc.removeObserver(this, "profile-before-change");
     this._observerSvc.removeObserver(this, "xpcom-shutdown");
     this._observerSvc.removeObserver(this, "profile-do-change");
+    this._observerSvc.removeObserver(this, "handlersvc-rdf-replace");
 
     // XXX Should we also null references to all the services that get stored
     // by our memoizing getters in the Convenience Getters section?
   },
 
   _onProfileChange: function HS__onProfileChange() {
     // Lose our reference to the datasource so we reacquire it
     // from the new profile the next time we need it.
@@ -284,17 +285,24 @@ HandlerService.prototype = {
       case "profile-before-change":
         this._onProfileChange();
         break;
       case "xpcom-shutdown":
         this._destroy();
         break;
       case "profile-do-change":
         this._updateDB();
-        break;  
+        break;
+      case "handlersvc-rdf-replace":
+        if (this.__ds) {
+          this._rdf.UnregisterDataSource(this.__ds);
+          this.__ds = null;
+        }
+        this._observerSvc.notifyObservers(null, "handlersvc-rdf-replace-complete", null);
+        break;
     }
   },
 
 
   //**************************************************************************//
   // nsIHandlerService
 
   enumerate: function HS_enumerate() {
@@ -308,17 +316,18 @@ HandlerService.prototype = {
   fillHandlerInfo: function HS_fillHandlerInfo(aHandlerInfo, aOverrideType) {
     var type = aOverrideType || aHandlerInfo.type;
     var typeID = this._getTypeID(this._getClass(aHandlerInfo), type);
 
     // Determine whether or not information about this handler is available
     // in the datastore by looking for its "value" property, which stores its
     // type and should always be present.
     if (!this._hasValue(typeID, NC_VALUE))
-      throw Cr.NS_ERROR_NOT_AVAILABLE;
+      throw new Components.Exception("handlerSvc fillHandlerInfo: don't know this type",
+                                     Cr.NS_ERROR_NOT_AVAILABLE);
 
     // Retrieve the human-readable description of the type.
     if (this._hasValue(typeID, NC_DESCRIPTION))
       aHandlerInfo.description = this._getValue(typeID, NC_DESCRIPTION);
 
     // Note: for historical reasons, we don't actually check that the type
     // record has a "handlerProp" property referencing the info record.  It's
     // unclear whether or not we should start doing this check; perhaps some
@@ -683,24 +692,40 @@ HandlerService.prototype = {
       default:
         this._removeTarget(infoID, NC_SAVE_TO_DISK);
         this._removeTarget(infoID, NC_HANDLE_INTERNALLY);
         this._removeTarget(infoID, NC_USE_SYSTEM_DEFAULT);
         break;
     }
   },
 
+  _handlerAppIsUnknownType: function HS__handlerAppIsUnknownType(aHandlerApp) {
+    if (aHandlerApp instanceof Ci.nsILocalHandlerApp ||
+        aHandlerApp instanceof Ci.nsIWebHandlerApp ||
+        aHandlerApp instanceof Ci.nsIDBusHandlerApp) {
+      return false;
+    } else {
+      return true;
+    }
+  },
+
+
   _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;
+      }
       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
@@ -739,16 +764,21 @@ HandlerService.prototype = {
     }
 
     // Next, store any new handler apps.
     var newHandlerApps =
       aHandlerInfo.possibleApplicationHandlers.enumerate();
     while (newHandlerApps.hasMoreElements()) {
       let handlerApp =
         newHandlerApps.getNext().QueryInterface(Ci.nsIHandlerApp);
+      // If the handlerApp is an unknown type, ignore it.
+      // Android default application handler is the case.
+      if (this._handlerAppIsUnknownType(handlerApp)) {
+        continue;
+      }
       let handlerAppID = this._getPossibleHandlerAppID(handlerApp);
       if (!this._hasResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID)) {
         this._storeHandlerApp(handlerAppID, handlerApp);
         this._addResourceAssertion(infoID, NC_POSSIBLE_APP, handlerAppID);
       }
       delete currentHandlerApps[handlerAppID];
     }
 
@@ -907,16 +937,18 @@ HandlerService.prototype = {
       var file = this._dirSvc.get("UMimTyp", Ci.nsIFile);
       // FIXME: make this a memoizing getter if we use it anywhere else.
       var ioService = Cc["@mozilla.org/network/io-service;1"].
                       getService(Ci.nsIIOService);
       var fileHandler = ioService.getProtocolHandler("file").
                         QueryInterface(Ci.nsIFileProtocolHandler);
       this.__ds =
         this._rdf.GetDataSourceBlocking(fileHandler.getURLSpecFromFile(file));
+      // do any necessary updating of the datastore
+      this._updateDB();
     }
 
     return this.__ds;
   },
 
 
   //**************************************************************************//
   // Datastore Utils
new file mode 100644
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/common_test_handlerService.js
@@ -0,0 +1,488 @@
+/* -*- 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).
+ */
+
+"use strict"
+
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://testing-common/TestUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gMIMEService",
+                                   "@mozilla.org/mime;1",
+                                   "nsIMIMEService");
+XPCOMUtils.defineLazyServiceGetter(this, "gExternalProtocolService",
+                                   "@mozilla.org/uriloader/external-protocol-service;1",
+                                   "nsIExternalProtocolService");
+
+const pdfHandlerInfo = gMIMEService.getFromTypeAndExtension("application/pdf", null);
+const gzipHandlerInfo = gMIMEService.getFromTypeAndExtension("application/x-gzip", null);
+const ircHandlerInfo = gExternalProtocolService.getProtocolHandlerInfo("irc");
+
+let executable = HandlerServiceTest._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;
+  }
+};
+
+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;
+  }
+};
+
+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;
+  }
+};
+
+function run_test() {
+  do_get_profile();
+  run_next_test();
+}
+
+/**
+ * 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.
+ */
+function getBlankHandlerInfo(type) {
+  let handlerInfo = gMIMEService.getFromTypeAndExtension(type, null);
+  do_check_true(handlerInfo.alwaysAskBeforeHandling);
+
+  if (handlerInfo.possibleApplicationHandlers.length) {
+    let apps = handlerInfo.possibleApplicationHandlers.enumerate();
+    let app = apps.getNext().QueryInterface(Ci.nsIHandlerApp);
+    do_check_eq(app.name, "Android chooser");
+  } else {
+    do_check_eq(handlerInfo.preferredAction, Ci.nsIHandlerInfo.saveToDisk);
+  }
+  return handlerInfo;
+}
+
+/**
+ * Get a handler info for a protocol that neither the application nor the OS
+ * knows about and make sure its properties are set to the proper default
+ * values.
+ */
+function getBlankHandlerInfoForProtocol(type) {
+  let handlerInfo = gExternalProtocolService.getProtocolHandlerInfo(type);
+  do_check_true(handlerInfo.alwaysAskBeforeHandling);
+
+  if (handlerInfo.possibleApplicationHandlers.length) {
+    let apps = handlerInfo.possibleApplicationHandlers.enumerate();
+    let app = apps.getNext().QueryInterface(Ci.nsIHandlerApp);
+    do_check_eq(app.name, "Android chooser");
+  } else {
+    do_check_eq(handlerInfo.preferredAction, Ci.nsIHandlerInfo.alwaysAsk);
+  }
+
+  return handlerInfo;
+}
+
+function getAllTypesByEnumerate() {
+  let handlerInfos = gHandlerService.enumerate();
+  let types = [];
+  while (handlerInfos.hasMoreElements()) {
+    let handlerInfo = handlerInfos.getNext().QueryInterface(Ci.nsIHandlerInfo);
+    types.push(handlerInfo.type);
+  }
+  return types.sort();
+}
+
+// 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(getAllTypesByEnumerate(), ["application/pdf", "irc", "ircs", "mailto", "webcal"]);
+
+  // II. do modifications first and reload the DS again
+  gHandlerService.store(gzipHandlerInfo);
+  gHandlerService.remove(pdfHandlerInfo);
+  yield reloadData();
+  Assert.deepEqual(getAllTypesByEnumerate(), ["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(getAllTypesByEnumerate(), ["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, "application/pdf");
+
+  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, "application/pdf");
+});
+
+// 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 = getBlankHandlerInfo("nonexistent/type");
+  let handlerInfo2 = 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");
+  do_check_eq(handlerInfo.preferredAction, Ci.nsIHandlerInfo.useSystemDefault);
+  if (Services.appinfo.widgetToolkit == "android") {
+    do_check_eq(handlerInfo.possibleApplicationHandlers.length, 2);
+  } else {
+    do_check_eq(handlerInfo.possibleApplicationHandlers.length, 1);
+  }
+  do_check_false(handlerInfo.alwaysAskBeforeHandling);
+  let extEnumerator = handlerInfo.getFileExtensions();
+  do_check_eq(extEnumerator.getNext(), ".type2");
+  do_check_false(extEnumerator.hasMore());
+  do_check_eq(handlerInfo.preferredApplicationHandler.name, "Local Handler");
+  let apps = handlerInfo.possibleApplicationHandlers.enumerate();
+  let app;
+  if (Services.appinfo.widgetToolkit == "android") {
+    app = apps.getNext().QueryInterface(Ci.nsIHandlerApp);
+    do_check_eq(app.name, "Android chooser");
+  }
+  app = apps.getNext().QueryInterface(Ci.nsILocalHandlerApp);
+  do_check_eq(executable.path, app.executable.path);
+  do_check_eq(app.name, localHandler.name);
+  do_check_false(apps.hasMoreElements());
+});
+
+// Test the functionality of fillHandlerInfo :
+// - Check the failure case by requesting a non-existent handler type
+add_task(function* testFillHandlerInfoWithError() {
+  yield removeImportDB();
+
+  let handlerInfo = 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 = getBlankHandlerInfo("nonexistent/type");
+  let handlerInfo2 = getBlankHandlerInfo("nonexistent/type2");
+  handlerInfo2.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+  handlerInfo2.preferredApplicationHandler = webHandler;
+  handlerInfo2.possibleApplicationHandlers.appendElement(localHandler, false);
+  handlerInfo2.possibleApplicationHandlers.appendElement(webHandler, false);
+  handlerInfo2.alwaysAskBeforeHandling = false;
+  gHandlerService.store(handlerInfo2);
+
+  gHandlerService.fillHandlerInfo(handlerInfo, "nonexistent/type2");
+  let apps = handlerInfo.possibleApplicationHandlers.enumerate();
+  let app;
+  if (Services.appinfo.widgetToolkit == "android") {
+    app = apps.getNext().QueryInterface(Ci.nsIHandlerApp);
+    do_check_eq(app.name, "Android chooser");
+  }
+  app = apps.getNext().QueryInterface(Ci.nsIHandlerApp);
+  do_check_eq(app.name, webHandler.name);
+  app = apps.getNext().QueryInterface(Ci.nsIHandlerApp);
+  do_check_eq(app.name, localHandler.name);
+  do_check_false(apps.hasMoreElements());
+});
+
+// Verify the handling of app handler: web handler
+add_task(function* testStoreForWebHandler() {
+  yield removeImportDB();
+
+  let handlerInfo = getBlankHandlerInfo("nonexistent/type");
+  let handlerInfo2 = getBlankHandlerInfo("nonexistent/type2");
+  handlerInfo2.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+  handlerInfo2.preferredApplicationHandler = webHandler;
+  handlerInfo2.alwaysAskBeforeHandling = false;
+  gHandlerService.store(handlerInfo2);
+
+  gHandlerService.fillHandlerInfo(handlerInfo, "nonexistent/type2");
+  let apps = handlerInfo.possibleApplicationHandlers.enumerate();
+  let app;
+  if (Services.appinfo.widgetToolkit == "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);
+});
+
+// Verify the handling of app handler: DBus handler
+add_task(function* testStoreForDBusHandler() {
+  if (!("@mozilla.org/uriloader/dbus-handler-app;1" in Cc)) {
+    do_print("Skipping test because it does not apply to this platform.");
+    return;
+  }
+
+  yield removeImportDB();
+
+  let handlerInfo = getBlankHandlerInfo("nonexistent/type");
+  let handlerInfo2 = getBlankHandlerInfo("nonexistent/type2");
+  handlerInfo2.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+  handlerInfo2.preferredApplicationHandler = dBusHandler;
+  handlerInfo2.alwaysAskBeforeHandling = false;
+  gHandlerService.store(handlerInfo2);
+
+  gHandlerService.fillHandlerInfo(handlerInfo, "nonexistent/type2");
+  let app = handlerInfo.preferredApplicationHandler.QueryInterface(Ci.nsIDBusHandlerApp);
+  do_check_eq(app.name, dBusHandler.name);
+  do_check_eq(app.service, dBusHandler.service);
+  do_check_eq(app.method, dBusHandler.method);
+  do_check_eq(app.objectPath, dBusHandler.objectPath);
+  do_check_eq(app.dBusInterface, dBusHandler.dBusInterface);
+});
+
+// Test the functionality of _IsInHandlerArray() by injecting default handler again
+// Since we don't have defaultHandlersVersion pref on Android, skip this test.
+add_task(function* testIsInHandlerArray() {
+  if (Services.appinfo.widgetToolkit == "android") {
+    do_print("Skipping test because it does not apply to this platform.");
+    return;
+  }
+
+  yield removeImportDB();
+
+  let protoInfo = getBlankHandlerInfoForProtocol("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 osDefaultHandlerFound = {};
+  let ircInfo = gExternalProtocolService.getProtocolHandlerInfoFromOS("irc",
+                                                                      osDefaultHandlerFound);
+  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 = getBlankHandlerInfoForProtocol("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.
+add_task(function* testFillHandlerInfoForProtocol() {
+  if (Services.appinfo.widgetToolkit == "android") {
+    do_print("Skipping test because it does not apply to this platform.");
+    return;
+  }
+
+  yield removeImportDB();
+
+  let osDefaultHandlerFound = {};
+  let protoInfo = getBlankHandlerInfoForProtocol("nonexistent");
+
+  let ircInfo = gExternalProtocolService.getProtocolHandlerInfoFromOS("irc",
+                                                                      osDefaultHandlerFound);
+  do_check_true(gHandlerService.exists(ircInfo));
+
+  gHandlerService.fillHandlerInfo(protoInfo, "irc");
+  do_check_true(protoInfo.alwaysAskBeforeHandling);
+  let possibleHandlers = protoInfo.possibleApplicationHandlers;
+  do_check_eq(possibleHandlers.length, 1);
+  let app = possibleHandlers.enumerate().getNext().QueryInterface(Ci.nsIWebHandlerApp);
+  do_check_eq(app.name, "Mibbit");
+  do_check_eq(app.uriTemplate, "https://www.mibbit.com/?url=%s");
+});
+
+
+// Test the functionality of store() and fillHandlerInfo for protocol
+add_task(function* testStoreForProtocol() {
+  yield removeImportDB();
+
+  let protoInfo = getBlankHandlerInfoForProtocol("nonexistent");
+  let protoInfo2 = getBlankHandlerInfoForProtocol("nonexistent2");
+  protoInfo2.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+  protoInfo2.alwaysAskBeforeHandling = false;
+  protoInfo2.preferredApplicationHandler = webHandler;
+  gHandlerService.store(protoInfo2);
+
+  yield reloadData();
+  do_check_true(gHandlerService.exists(protoInfo2));
+
+  gHandlerService.fillHandlerInfo(protoInfo, "nonexistent2");
+  do_check_eq(protoInfo.preferredAction, Ci.nsIHandlerInfo.useHelperApp);
+  do_check_false(protoInfo.alwaysAskBeforeHandling);
+  do_check_eq(protoInfo.preferredApplicationHandler.name, webHandler.name);
+  let apps = protoInfo.possibleApplicationHandlers.enumerate();
+  let app;
+  if (Services.appinfo.widgetToolkit == "android") {
+    app = apps.getNext().QueryInterface(Ci.nsIHandlerApp);
+    do_check_eq(app.name, "Android chooser");
+  }
+  app = apps.getNext().QueryInterface(Ci.nsIWebHandlerApp);
+  do_check_eq(app.name, webHandler.name);
+  do_check_eq(app.uriTemplate, webHandler.uriTemplate);
+});
+
+// Test the functionality of fillHandlerInfo when there is no overrideType
+add_task(function* testFillHandlerInfoWithoutOverrideType() {
+  yield removeImportDB();
+
+  // mimeType
+  let mimeInfo = getBlankHandlerInfo("nonexistent/type");
+  let storedHandlerInfo = getBlankHandlerInfo("nonexistent/type");
+  storedHandlerInfo.preferredAction = Ci.nsIHandlerInfo.useSystemDefault;
+  storedHandlerInfo.preferredApplicationHandler = webHandler;
+  storedHandlerInfo.alwaysAskBeforeHandling = false;
+  gHandlerService.store(storedHandlerInfo);
+
+  // protocol type
+  let protoInfo = getBlankHandlerInfoForProtocol("nonexistent");
+  let storedProtoInfo = getBlankHandlerInfoForProtocol("nonexistent");
+  storedProtoInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
+  storedProtoInfo.alwaysAskBeforeHandling = false;
+  storedProtoInfo.preferredApplicationHandler = webHandler;
+  gHandlerService.store(storedProtoInfo);
+
+  // Get handlerInfo by fillHandlerInfo without overrideType
+  for (let handlerInfo of [mimeInfo, protoInfo]) {
+    let handlerInfo2 = storedProtoInfo;
+    if (handlerInfo.type == "nonexistent/type") {
+      handlerInfo2 = storedHandlerInfo;
+    }
+    gHandlerService.fillHandlerInfo(handlerInfo, null);
+    do_check_eq(handlerInfo.preferredAction, handlerInfo2.preferredAction);
+    do_check_eq(handlerInfo.alwaysAskBeforeHandling,
+                handlerInfo2.alwaysAskBeforeHandling);
+    do_check_eq(handlerInfo.preferredApplicationHandler.name,
+                handlerInfo2.preferredApplicationHandler.name);
+    let apps = handlerInfo.possibleApplicationHandlers.enumerate();
+    let app;
+    if (Services.appinfo.widgetToolkit == "android") {
+      app = apps.getNext().QueryInterface(Ci.nsIHandlerApp);
+      do_check_eq(app.name, "Android chooser");
+    }
+    app = apps.getNext().QueryInterface(Ci.nsIWebHandlerApp);
+    do_check_eq(app.name, webHandler.name);
+    do_check_eq(app.uriTemplate, webHandler.uriTemplate);
+  }
+});
+
+// Test the functionality of fillHandlerInfo() :
+// - Use "nsIHandlerInfo.useHelperApp" to replace "nsIHandlerInfo.alwaysAsk" for handlerInfo.preferredAction
+// - Use "nsIHandlerInfo.useHelperApp" to replace unknow action for handlerInfo.preferredAction
+add_task(function* testPreferredActionHandling() {
+  yield removeImportDB();
+
+  let protoInfo = getBlankHandlerInfoForProtocol("nonexistent");
+  let protoInfo2 = getBlankHandlerInfoForProtocol("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);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/handlers.json
@@ -0,0 +1,1 @@
+{"version":{"en-US":999},"mimetypes":{"application/pdf":{"description":"PDF document","action":3,"askBeforeHandling":false,"fileExtensions":["pdf"]}},"schemes":{"webcal":{"action":1,"askBeforeHandling":true,"preferredHandler":{"name":"30 Boxes","uriTemplate":"http://30boxes.com/external/widget?refer=ff&amp;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"}]}}}
--- a/uriloader/exthandler/tests/unit/head_handlerService.js
+++ b/uriloader/exthandler/tests/unit/head_handlerService.js
@@ -47,16 +47,17 @@ var HandlerServiceTest = {
   // Initialization & Destruction
 
   init: function HandlerServiceTest_init() {
     // Register ourselves as a directory provider for the datasource file
     // if there isn't one registered already.
     try {
       this._dirSvc.get("UMimTyp", Ci.nsIFile);
     } catch (ex) {
+      do_get_profile();
       this._dirSvc.registerProvider(this);
       this._providerRegistered = true;
     }
 
     // Delete the existing datasource file, if any, so we start from scratch.
     // We also do this after finishing the tests, so there shouldn't be an old
     // file lying around, but just in case we delete it here as well.
     this._deleteDatasourceFile();
@@ -81,17 +82,17 @@ var HandlerServiceTest = {
   // nsIDirectoryServiceProvider
 
   getFile: function HandlerServiceTest_getFile(property, persistent) {
     this.log("getFile: requesting " + property);
 
     persistent.value = true;
 
     if (property == "UMimTyp") {
-      var datasourceFile = this._dirSvc.get("CurProcD", Ci.nsIFile);
+      var datasourceFile = this._dirSvc.get("ProfD", Ci.nsIFile);
       datasourceFile.append("mimeTypes.rdf");
       return datasourceFile;
     }
 
     // This causes extraneous errors to show up in the log when the directory
     // service asks us first for CurProcD and MozBinD.  I wish there was a way
     // to suppress those errors.
     this.log("the following NS_ERROR_FAILURE exception in " +
new file mode 100644
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/mimeTypes.rdf
@@ -0,0 +1,79 @@
+<?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: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:mimetype:application/pdf"
+                   NC:value="application/pdf"
+                   NC:fileExtensions="pdf">
+    <NC:handlerProp RDF:resource="urn:mimetype:handler:application/pdf"/>
+  </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:root"
+                   NC:en-US_defaultHandlersVersion="999" />
+  <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:mailto"
+                   NC:value="mailto">
+    <NC:handlerProp RDF:resource="urn:scheme:handler:mailto"/>
+  </RDF:Description>
+  <RDF:Seq RDF:about="urn:schemes:root">
+    <RDF:li RDF:resource="urn:scheme:webcal"/>
+    <RDF:li RDF:resource="urn:scheme:ircs"/>
+    <RDF:li RDF:resource="urn:scheme:mailto"/>
+    <RDF:li RDF:resource="urn:scheme:irc"/>
+  </RDF:Seq>
+  <RDF:Description RDF:about="urn:scheme:webcal"
+                   NC:value="webcal">
+    <NC:handlerProp RDF:resource="urn:scheme:handler:webcal"/>
+  </RDF:Description>
+  <RDF:Seq RDF:about="urn:mimetypes:root">
+    <RDF:li RDF:resource="urn:mimetype:application/pdf"/>
+  </RDF:Seq>
+  <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:Description RDF:about="urn:schemes">
+    <NC:Protocol-Schemes RDF:resource="urn:schemes:root"/>
+  </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:scheme:handler:webcal"
+                   NC:alwaysAsk="true">
+    <NC:possibleApplication RDF:resource="urn:handler:web:https://30boxes.com/external/widget?refer=ff&amp;url=%s"/>
+    <NC:externalApplication RDF:resource="urn:scheme:externalApplication:webcal"/>
+  </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: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:handler:application/pdf"
+                   NC:handleInternal="true"
+                   NC:alwaysAsk="false" />
+  <RDF:Description RDF:about="urn:mimetypes">
+    <NC:MIME-types RDF:resource="urn:mimetypes:root"/>
+  </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:ircs"
+                   NC:value="ircs">
+    <NC:handlerProp RDF:resource="urn:scheme:handler:ircs"/>
+  </RDF:Description>
+  <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:RDF>
new file mode 100644
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_handlerService_json.js
@@ -0,0 +1,46 @@
+/* -*- 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.
+ */
+
+"use strict"
+
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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);
+
+var prepareImportDB = Task.async(function* () {
+  yield reloadData();
+
+  let dst = OS.Path.join(OS.Constants.Path.profileDir, "handlers.json");
+  yield OS.File.copy(do_get_file("handlers.json").path, dst);
+  Assert.ok((yield OS.File.exists(dst)), "should have a DB now");
+});
+
+var removeImportDB = Task.async(function* () {
+  yield reloadData();
+
+  let dst = OS.Path.join(OS.Constants.Path.profileDir, "handlers.json");
+  yield OS.File.remove(dst);
+  Assert.ok(!(yield OS.File.exists(dst)), "should not have a DB now");
+});
+
+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", null);
+  yield promise;
+});
new file mode 100644
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/test_handlerService_rdf.js
@@ -0,0 +1,44 @@
+/* -*- 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.
+ */
+
+"use strict"
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+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);
+
+var prepareImportDB = Task.async(function* () {
+  yield reloadData();
+
+  let dst = HandlerServiceTest._dirSvc.get("UMimTyp", Ci.nsIFile);
+  yield OS.File.copy(do_get_file("mimeTypes.rdf").path, dst.path);
+  Assert.ok((yield OS.File.exists(dst.path)), "should have a DB now");
+});
+
+var removeImportDB = Task.async(function* () {
+  yield reloadData();
+  HandlerServiceTest._deleteDatasourceFile();
+  let dst = HandlerServiceTest._dirSvc.get("UMimTyp", Ci.nsIFile);
+  Assert.ok(!(yield OS.File.exists(dst.path)), "should not have a DB now");
+});
+
+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", null);
+  yield promise;
+});
--- a/uriloader/exthandler/tests/unit/xpcshell.ini
+++ b/uriloader/exthandler/tests/unit/xpcshell.ini
@@ -1,14 +1,20 @@
 [DEFAULT]
 head = head_handlerService.js
 run-sequentially = Bug 912235 - Intermittent failures
+firefox-appdir = browser
+support-files = common_test_handlerService.js
 
 [test_getTypeFromExtension_ext_to_type_mapping.js]
 [test_getTypeFromExtension_with_empty_Content_Type.js]
 [test_badMIMEType.js]
 [test_handlerService.js]
 support-files = mailcap
 # Bug 676997: test consistently fails on Android
 fail-if = os == "android"
+[test_handlerService_json.js]
+support-files = handlers.json
+[test_handlerService_rdf.js]
+support-files = mimeTypes.rdf
 [test_punycodeURIs.js]
 # Bug 676997: test consistently fails on Android
 fail-if = os == "android"