bug 391152: support enumeration and removal of known handlers; r=biesi, sr=dmose
authormyk@mozilla.org
Tue, 14 Aug 2007 13:21:29 -0700
changeset 4641 66052ebe1c348996cc655357c840ef98e3299a3e
parent 4640 ddecaa31dd4c8bca4d274a550dfbf31f34e56731
child 4642 a9da2d9d01fa1624dde6e36db5815dfaec0c3da1
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbiesi, dmose
bugs391152
milestone1.9a8pre
bug 391152: support enumeration and removal of known handlers; r=biesi, sr=dmose
uriloader/exthandler/nsHandlerService.js
uriloader/exthandler/nsIHandlerService.idl
uriloader/exthandler/tests/unit/head_handlerService.js
uriloader/exthandler/tests/unit/tail_handlerService.js
uriloader/exthandler/tests/unit/test_handlerService.js
--- a/uriloader/exthandler/nsHandlerService.js
+++ b/uriloader/exthandler/nsHandlerService.js
@@ -35,29 +35,31 @@
  * ***** END LICENSE BLOCK ***** */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 
+const CLASS_MIMEINFO        = "mimetype";
+const CLASS_PROTOCOLINFO    = "scheme";
+
+
 // namespace prefix
 const NC_NS                 = "http://home.netscape.com/NC-rdf#";
 
 // type list properties
 
 const NC_MIME_TYPES         = NC_NS + "MIME-types";
 const NC_PROTOCOL_SCHEMES   = NC_NS + "Protocol-Schemes";
 
 // content type ("type") properties
 
-const NC_EDITABLE           = NC_NS + "editable";
-
-// nsIMIMEInfo::MIMEType
+// nsIHandlerInfo::type
 const NC_VALUE              = NC_NS + "value";
 
 // references nsIHandlerInfo record
 const NC_HANDLER_INFO       = NC_NS + "handlerProp";
 
 // handler info ("info") properties
 
 // nsIHandlerInfo::preferredAction
@@ -96,26 +98,65 @@ HandlerService.prototype = {
   classID:          Components.ID("{32314cc8-22f7-4f7f-a645-1a45453ba6a6}"),
   contractID:       "@mozilla.org/uriloader/handler-service;1",
   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIHandlerService]),
 
 
   //**************************************************************************//
   // nsIHandlerService
 
+  enumerate: function HS_enumerate() {
+    var handlers = Cc["@mozilla.org/array;1"].
+                   createInstance(Ci.nsIMutableArray);
+    this._appendHandlers(handlers, CLASS_MIMEINFO);
+    this._appendHandlers(handlers, CLASS_PROTOCOLINFO);
+    return handlers.enumerate();
+  },
+
   store: function HS_store(aHandlerInfo) {
     // FIXME: when we switch from RDF to something with transactions (like
     // SQLite), enclose the following changes in a transaction so they all
     // get rolled back if any of them fail and we don't leave the datastore
     // in an inconsistent state.
 
     this._ensureRecordsForType(aHandlerInfo);
     this._storePreferredAction(aHandlerInfo);
     this._storePreferredHandler(aHandlerInfo);
     this._storeAlwaysAsk(aHandlerInfo);
+
+    // Write the changes to the database immediately so we don't lose them
+    // if the application crashes.
+    if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
+      this._ds.Flush();
+  },
+
+  remove: function HS_remove(aHandlerInfo) {
+    var preferredHandlerID = this._getPreferredHandlerID(aHandlerInfo);
+    this._removeAssertions(preferredHandlerID);
+
+    var infoID = this._getInfoID(aHandlerInfo);
+    this._removeAssertions(infoID);
+
+    var typeID = this._getTypeID(aHandlerInfo);
+    this._removeAssertions(typeID);
+
+    // Now that there's no longer a handler for this type, remove the type
+    // from the list of types for which there are known handlers.
+    var typeList = this._ensureAndGetTypeList(this._getClass(aHandlerInfo));
+    var type = this._rdf.GetResource(typeID);
+    var typeIndex = typeList.IndexOf(type);
+    if (typeIndex != -1)
+      typeList.RemoveElementAt(typeIndex, true);
+
+    // Write the changes to the database immediately so we don't lose them
+    // if the application crashes.
+    // XXX If we're removing a bunch of handlers at once, will flushing
+    // after every removal cause a significant performance hit?
+    if (this._ds instanceof Ci.nsIRDFRemoteDataSource)
+      this._ds.Flush();
   },
 
 
   //**************************************************************************//
   // Storage Methods
 
   _storePreferredAction: function HS__storePreferredAction(aHandlerInfo) {
     var infoID = this._getInfoID(aHandlerInfo);
@@ -191,17 +232,37 @@ HandlerService.prototype = {
     var infoID = this._getInfoID(aHandlerInfo);
     this._setLiteral(infoID,
                      NC_ALWAYS_ASK,
                      aHandlerInfo.alwaysAskBeforeHandling ? "true" : "false");
   },
 
 
   //**************************************************************************//
-  // Storage Utils
+  // Convenience Getters
+
+  // MIME Service
+  __mimeSvc: null,
+  get _mimeSvc() {
+    if (!this.__mimeSvc)
+      this.__mimeSvc =
+        Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
+        getService(Ci.nsIMIMEService);
+    return this.__mimeSvc;
+  },
+
+  // Protocol Service
+  __protocolSvc: null,
+  get _protocolSvc() {
+    if (!this.__protocolSvc)
+      this.__protocolSvc =
+        Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+        getService(Ci.nsIExternalProtocolService);
+    return this.__protocolSvc;
+  },
 
   // RDF Service
   __rdf: null,
   get _rdf() {
     if (!this.__rdf)
       this.__rdf = Cc["@mozilla.org/rdf/rdf-service;1"].
                    getService(Ci.nsIRDFService);
     return this.__rdf;
@@ -230,53 +291,57 @@ HandlerService.prototype = {
                         QueryInterface(Ci.nsIFileProtocolHandler);
       this.__ds =
         this._rdf.GetDataSourceBlocking(fileHandler.getURLSpecFromFile(file));
     }
 
     return this.__ds;
   },
 
+
+  //**************************************************************************//
+  // Storage Utils
+
   /**
    * Get the string identifying whether this is a MIME or a protocol handler.
    * This string is used in the URI IDs of various RDF properties.
    * 
    * @param aHandlerInfo {nsIHandlerInfo} the handler for which to get the class
    * 
    * @returns {string} the ID
    */
   _getClass: function HS__getClass(aHandlerInfo) {
     if (aHandlerInfo instanceof Ci.nsIMIMEInfo)
-      return "mimetype";
+      return CLASS_MIMEINFO;
     else
-      return "scheme";
+      return CLASS_PROTOCOLINFO;
   },
 
   /**
    * Return the unique identifier for a content type record, which stores
-   * the editable and value fields plus a reference to the type's handler.
+   * the value field plus a reference to the type's handler.
    * 
-   * |urn:(mimetype|scheme):<type>|
+   * |urn:<class>:<type>|
    * 
    * XXX: should this be a property of nsIHandlerInfo?
    * 
    * @param aHandlerInfo {nsIHandlerInfo} the type for which to get the ID
    * 
    * @returns {string} the ID
    */
   _getTypeID: function HS__getTypeID(aHandlerInfo) {
     return "urn:" + this._getClass(aHandlerInfo) + ":" + aHandlerInfo.type;
   },
 
   /**
    * Return the unique identifier for a type info record, which stores
    * the preferredAction and alwaysAsk fields plus a reference to the preferred
    * handler.  Roughly equivalent to the nsIHandlerInfo interface.
    * 
-   * |urn:(mimetype|scheme):handler:<type>|
+   * |urn:<class>:handler:<type>|
    * 
    * FIXME: the type info record should be merged into the type record,
    * since there's a one to one relationship between them, and this record
    * merely stores additional attributes of a content type.
    * 
    * @param aHandlerInfo {nsIHandlerInfo} the handler for which to get the ID
    * 
    * @returns {string} the ID
@@ -287,17 +352,17 @@ HandlerService.prototype = {
   },
 
   /**
    * Return the unique identifier for a preferred handler record, which stores
    * information about the preferred handler for a given content type, including
    * its human-readable name and the path to its executable (for a local app)
    * or its URI template (for a web app).
    * 
-   * |urn:(mimetype|scheme):externalApplication:<type>|
+   * |urn:<class>:externalApplication:<type>|
    *
    * XXX: should this be a property of nsIHandlerApp?
    *
    * FIXME: this should be an arbitrary ID, and we should retrieve it from
    * the datastore for a given content type via the NC:ExternalApplication
    * property rather than looking for a specific ID, so a handler doesn't
    * have to change IDs when it goes from being a possible handler to being
    * the preferred one (once we support possible handlers).
@@ -307,36 +372,36 @@ HandlerService.prototype = {
    * @returns {string} the ID
    */
   _getPreferredHandlerID: function HS__getPreferredHandlerID(aHandlerInfo) {
     return "urn:" + this._getClass(aHandlerInfo) + ":externalApplication:" +
            aHandlerInfo.type;
   },
 
   /**
-   * Get the list of types for the given class, creating the list if it
-   * doesn't already exist.  The class can be either "mimetype" or "scheme"
+   * Get the list of types for the given class, creating the list if it doesn't
+   * already exist. The class can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO
    * (i.e. the result of a call to _getClass).
    * 
-   * |urn:(mimetype|scheme)s|
-   * |urn:(mimetype|scheme)s:root|
+   * |urn:<class>s|
+   * |urn:<class>s:root|
    * 
    * @param aClass {string} the class for which to retrieve a list of types
    *
    * @returns {nsIRDFContainer} the list of types
    */
   _ensureAndGetTypeList: function HS__ensureAndGetTypeList(aClass) {
     // FIXME: once nsIHandlerInfo supports retrieving the scheme
     // (and differentiating between MIME and protocol content types),
     // implement support for protocols.
 
     var source = this._rdf.GetResource("urn:" + aClass + "s");
     var property =
-      this._rdf.GetResource(aClass == "mimetype" ? NC_MIME_TYPES
-                                                 : NC_PROTOCOL_SCHEMES);
+      this._rdf.GetResource(aClass == CLASS_MIMEINFO ? NC_MIME_TYPES
+                                                     : NC_PROTOCOL_SCHEMES);
     var target = this._rdf.GetResource("urn:" + aClass + "s:root");
 
     // Make sure we have an arc from the source to the target.
     if (!this._ds.HasAssertion(source, property, target, true))
       this._ds.Assert(source, property, target, true);
 
     // Make sure the target is a container.
     if (!this._containerUtils.IsContainer(this._ds, target))
@@ -370,17 +435,16 @@ HandlerService.prototype = {
     // don't need to do anything more.
     var typeID = this._getTypeID(aHandlerInfo);
     var type = this._rdf.GetResource(typeID);
     if (typeList.IndexOf(type) != -1)
       return;
 
     // Create a basic type record for this type.
     typeList.AppendElement(type);
-    this._setLiteral(typeID, NC_EDITABLE, "true");
     this._setLiteral(typeID, NC_VALUE, aHandlerInfo.type);
     
     // Create a basic info record for this type.
     var infoID = this._getInfoID(aHandlerInfo);
     this._setLiteral(infoID, NC_ALWAYS_ASK, "false");
     this._setResource(typeID, NC_HANDLER_INFO, infoID);
     // XXX Shouldn't we set preferredAction to useSystemDefault?
     // That's what it is if there's no record in the datastore; why should it
@@ -392,16 +456,78 @@ HandlerService.prototype = {
     // to require the record , but downloadactions.js::_ensureMIMERegistryEntry
     // used to create it, so we'll do the same.
     var preferredHandlerID = this._getPreferredHandlerID(aHandlerInfo);
     this._setLiteral(preferredHandlerID, NC_PATH, "");
     this._setResource(infoID, NC_PREFERRED_APP, preferredHandlerID);
   },
 
   /**
+   * Append known handlers of the given class to the given array.  The class
+   * can be either CLASS_MIMEINFO or CLASS_PROTOCOLINFO.
+   *
+   * @param aHandlers   {array} the array of handlers to append to
+   * @param aClass      {string} the class for which to append handlers
+   */
+  _appendHandlers: function HS__appendHandlers(aHandlers, aClass) {
+    var typeList = this._ensureAndGetTypeList(aClass);
+    var enumerator = typeList.GetElements();
+
+    while (enumerator.hasMoreElements()) {
+      var element = enumerator.getNext();
+      
+      // This should never happen.  If it does, that means our datasource
+      // is corrupted with type list entries that point to literal values
+      // instead of resources.  If it does happen, let's just do our best
+      // to recover by ignoring this entry and moving on to the next one.
+      if (!(element instanceof Ci.nsIRDFResource))
+        continue;
+
+      // Get the value of the element's NC:value property, which contains
+      // the MIME type or scheme for which we're retrieving a handler info.
+      var type = this._getValue(element.ValueUTF8, NC_VALUE);
+      if (!type)
+        continue;
+
+      var handler;
+      if (typeList.Resource.Value == "urn:mimetypes:root")
+        handler = this._mimeSvc.getFromTypeAndExtension(type, null);
+      else
+        handler = this._protocolSvc.getProtocolHandlerInfo(type);
+
+      aHandlers.appendElement(handler, false);
+    }
+  },
+
+  /**
+   * Get the value of a property of an RDF source.
+   *
+   * @param sourceURI   {string} the URI of the source
+   * @param propertyURI {string} the URI of the property
+   * @returns           {string} the value of the property
+   */
+  _getValue: function HS__getValue(sourceURI, propertyURI) {
+    var source = this._rdf.GetResource(sourceURI);
+    var property = this._rdf.GetResource(propertyURI);
+
+    var target = this._ds.GetTarget(source, property, true);
+
+    if (!target)
+      return null;
+    
+    if (target instanceof Ci.nsIRDFResource)
+      return target.ValueUTF8;
+
+    if (target instanceof Ci.nsIRDFLiteral)
+      return target.Value;
+
+    return null;
+  },
+
+  /**
    * Set a property of an RDF source to a literal value.
    *
    * @param sourceURI   {string} the URI of the source
    * @param propertyURI {string} the URI of the property
    * @param value       {string} the literal value
    */
   _setLiteral: function HS__setLiteral(sourceURI, propertyURI, value) {
     var source = this._rdf.GetResource(sourceURI);
@@ -451,17 +577,37 @@ HandlerService.prototype = {
    * @param propertyURI {string} the URI of the property
    */
   _removeValue: function HS__removeValue(sourceURI, propertyURI) {
     var source = this._rdf.GetResource(sourceURI);
     var property = this._rdf.GetResource(propertyURI);
 
     if (this._ds.hasArcOut(source, property)) {
       var target = this._ds.GetTarget(source, property, true);
-      this._ds.Unassert(source, property, target, true);
+      this._ds.Unassert(source, property, target);
+    }
+  },
+
+ /**
+  * Remove all assertions about a given RDF source.
+  *
+  * Note: not recursive.  If some assertions point to other resources,
+  * and you want to remove assertions about those resources too, you need
+  * to do so manually.
+  *
+  * @param sourceURI {string} the URI of the source
+  */
+  _removeAssertions: function HS__removeAssertions(sourceURI) {
+    var source = this._rdf.GetResource(sourceURI);
+    var properties = this._ds.ArcLabelsOut(source);
+ 
+    while (properties.hasMoreElements()) {
+      var property = properties.getNext();
+      var target = this._ds.GetTarget(source, property, true);
+      this._ds.Unassert(source, property, target);
     }
   },
 
 
   //**************************************************************************//
   // Utilities
 
   // FIXME: given that I keep copying them from JS component to JS component,
--- a/uriloader/exthandler/nsIHandlerService.idl
+++ b/uriloader/exthandler/nsIHandlerService.idl
@@ -32,27 +32,47 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsISupports.idl"
 
 interface nsIHandlerInfo;
+interface nsISimpleEnumerator;
 
-[scriptable, uuid(7c754635-139b-4d80-894e-6c71594ceb44)]
+[scriptable, uuid(18bd7cc7-04b5-46d1-ad95-386e51191eb2)]
 interface nsIHandlerService : nsISupports
 {
   /**
+   * Retrieve a list of all handlers in the datastore.  This list is not
+   * guaranteed to be in any particular order, and callers should not assume
+   * it will remain in the same order in the future.
+   *
+   * @returns a list of all handlers in the datastore
+   */
+  nsISimpleEnumerator enumerate();
+
+  /**
    * Save the preferred action, preferred handler, and always ask properties
    * of the given handler info object to the datastore.  Updates an existing
    * record or creates a new one if necessary.
    *
    * Note: if preferred action is undefined or invalid, then we assume
    * the default value nsIHandlerInfo::useHelperApp.
    *
    * FIXME: also store any changes to the list of possible handlers
    * (once we support possible handlers).
    *
    * @param aHandlerInfo  the handler info object
    */
   void store(in nsIHandlerInfo aHandlerInfo);
+
+  /**
+   * Remove the given handler info object from the datastore.  Deletes all
+   * records associated with the object, including the preferred handler, info,
+   * and type records plus the entry in the list of types, if they exist.
+   * Otherwise, it does nothing and does not return an error.
+   *
+   * @param aHandlerInfo  the handler info object
+   */
+  void remove(in nsIHandlerInfo aHandlerInfo);
 };
--- a/uriloader/exthandler/tests/unit/head_handlerService.js
+++ b/uriloader/exthandler/tests/unit/head_handlerService.js
@@ -44,17 +44,18 @@ const Cu = Components.utils;
 var HandlerServiceTest = {
   //**************************************************************************//
   // Convenience Getters
 
   __dirSvc: null,
   get _dirSvc() {
     if (!this.__dirSvc)
       this.__dirSvc = Cc["@mozilla.org/file/directory_service;1"].
-                      getService(Ci.nsIProperties);
+                      getService(Ci.nsIProperties).
+                      QueryInterface(Ci.nsIDirectoryService);
     return this.__dirSvc;
   },
 
   __consoleSvc: null,
   get _consoleSvc() {
     if (!this.__consoleSvc)
       this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
                           getService(Ci.nsIConsoleService);
@@ -70,16 +71,43 @@ var HandlerServiceTest = {
   QueryInterface: function HandlerServiceTest_QueryInterface(iid) {
     if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
       throw Cr.NS_ERROR_NO_INTERFACE;
     return this;
   },
 
 
   //**************************************************************************//
+  // 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) { this._dirSvc.registerProvider(this) }
+
+    // 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();
+
+    // Turn on logging so we can troubleshoot problems with the tests.
+    var prefBranch = Cc["@mozilla.org/preferences-service;1"].
+                     getService(Ci.nsIPrefBranch);
+    prefBranch.setBoolPref("browser.contentHandling.log", true);
+  },
+
+  destroy: function HandlerServiceTest_destroy() {
+    // Delete the existing datasource file, if any, so we don't leave test files
+    // lying around and we start from scratch the next time.
+    this._deleteDatasourceFile();
+  },
+
+
+  //**************************************************************************//
   // nsIDirectoryServiceProvider
 
   getFile: function HandlerServiceTest_getFile(property, persistent) {
     this.log("getFile: requesting " + property);
 
     persistent.value = true;
 
     if (property == "UMimTyp") {
@@ -97,33 +125,22 @@ var HandlerServiceTest = {
     throw Cr.NS_ERROR_FAILURE;
   },
 
 
   //**************************************************************************//
   // Utilities
 
   /**
-   * Get the datasource file, registering ourselves as a provider of the file
-   * if necessary.
+   * Delete the datasource file.
    */
-  getDatasourceFile: function HandlerServiceTest_getDatasourceFile() {
-    var datasourceFile;
-
-    try {
-      datasourceFile = this._dirSvc.get("UMimTyp", Ci.nsIFile);
-    }
-    catch (e) {}
-
-    if (!datasourceFile) {
-      this._dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(this);
-      datasourceFile = this._dirSvc.get("UMimTyp", Ci.nsIFile);
-    }
-
-    return datasourceFile;
+  _deleteDatasourceFile: function HandlerServiceTest__deleteDatasourceFile() {
+    var file = this._dirSvc.get("UMimTyp", Ci.nsIFile);
+    if (file.exists())
+      file.remove(false);
   },
 
   /**
    * Get the contents of the datasource as a serialized string.  Useful for
    * debugging problems with test failures, i.e.:
    *
    * HandlerServiceTest.log(HandlerServiceTest.getDatasourceContents());
    *
@@ -163,14 +180,9 @@ var HandlerServiceTest = {
   log: function HandlerServiceTest_log(message) {
     message = "*** HandlerServiceTest: " + message;
     this._consoleSvc.logStringMessage(message);
     print(message);
   }
 
 };
 
-HandlerServiceTest.getDatasourceFile();
-
-// Turn on logging so we can troubleshoot problems with the tests.
-var prefBranch = Cc["@mozilla.org/preferences-service;1"].
-                 getService(Ci.nsIPrefBranch);
-prefBranch.setBoolPref("browser.contentHandling.log", true);
+HandlerServiceTest.init();
new file mode 100755
--- /dev/null
+++ b/uriloader/exthandler/tests/unit/tail_handlerService.js
@@ -0,0 +1,37 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Mozilla browser.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Myk Melez <myk@mozilla.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+HandlerServiceTest.destroy();
--- a/uriloader/exthandler/tests/unit/test_handlerService.js
+++ b/uriloader/exthandler/tests/unit/test_handlerService.js
@@ -119,16 +119,37 @@ function run_test() {
   do_check_eq(typeof preferredHandler, "object");
   do_check_eq(preferredHandler.name, "Local Handler");
   do_check_true(preferredHandler instanceof Ci.nsILocalHandlerApp);
   preferredHandler.QueryInterface(Ci.nsILocalHandlerApp);
   do_check_eq(preferredHandler.executable.path, localHandler.executable.path);
 
   do_check_false(handlerInfo.alwaysAskBeforeHandling);
 
+  // Make sure the handler service's enumerate method lists all known handlers.
+  // FIXME: store and test enumeration of a protocol handler once bug 391150
+  // gets fixed and we can actually retrieve a protocol handler.
+  var handlerInfo2 = mimeSvc.getFromTypeAndExtension("nonexistent/type2", null);
+  handlerSvc.store(handlerInfo2);
+  var handlerTypes = ["nonexistent/type", "nonexistent/type2"];
+  var handlers = handlerSvc.enumerate();
+  while (handlers.hasMoreElements()) {
+    var handler = handlers.getNext().QueryInterface(Ci.nsIHandlerInfo);
+    do_check_neq(handlerTypes.indexOf(handler.type), -1);
+    handlerTypes.splice(handlerTypes.indexOf(handler.type), 1);
+  }
+  do_check_eq(handlerTypes.length, 0);
+
+  // Make sure the handler service's remove method removes a handler record.
+  handlerSvc.remove(handlerInfo2);
+  handlers = handlerSvc.enumerate();
+  while (handlers.hasMoreElements())
+    do_check_neq(handlers.getNext().QueryInterface(Ci.nsIHandlerInfo).type,
+                 handlerInfo2.type);
+
   // Make sure we can store and retrieve a handler info object with no preferred
   // handler.
   var noPreferredHandlerInfo =
     mimeSvc.getFromTypeAndExtension("nonexistent/no-preferred-handler", null);
   handlerSvc.store(noPreferredHandlerInfo);
   noPreferredHandlerInfo =
     mimeSvc.getFromTypeAndExtension("nonexistent/no-preferred-handler", null);
   do_check_eq(noPreferredHandlerInfo.preferredApplicationHandler, null);