Bug 1220237 - Remove uses of nsIEnumerator from PSM. r=keeler draft
authorCykesiopka <cykesiopka.bmo@gmail.com>
Wed, 24 Feb 2016 17:42:45 -0800
changeset 334379 1b9b40ea7a4ba2f6b0ffffa34fa6d72563ddf155
parent 334292 2b0aa1cffeeaabb524b9d2936321af18c7445fbc
child 514895 18079a297af964ae553826feee7c299823da2bad
push id11529
push usercykesiopka.bmo@gmail.com
push dateThu, 25 Feb 2016 01:55:46 +0000
reviewerskeeler
bugs1220237
milestone47.0a1
Bug 1220237 - Remove uses of nsIEnumerator from PSM. r=keeler MozReview-Commit-ID: 3FhBCqnJz4n
security/manager/pki/resources/content/device_manager.js
security/manager/pki/resources/content/password.js
security/manager/ssl/nsIPK11TokenDB.idl
security/manager/ssl/nsIPKCS11Module.idl
security/manager/ssl/nsIPKCS11ModuleDB.idl
security/manager/ssl/nsPK11TokenDB.cpp
security/manager/ssl/nsPKCS11Slot.cpp
security/manager/ssl/nsPKCS11Slot.h
security/manager/ssl/tests/unit/test_pkcs11_insert_remove.js
security/manager/ssl/tests/unit/test_pkcs11_list.js
security/manager/ssl/tests/unit/xpcshell-smartcards.ini
--- a/security/manager/pki/resources/content/device_manager.js
+++ b/security/manager/pki/resources/content/device_manager.js
@@ -66,56 +66,30 @@ function doConfirm(msg)
 {
   let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
     getService(Components.interfaces.nsIPromptService);
   return prompts.confirm(window, null, msg);
 }
 
 function RefreshDeviceList()
 {
-  var modules = secmoddb.listModules();
-  var done = false;
+  let modules = secmoddb.listModules();
+  while (modules.hasMoreElements()) {
+    let module = modules.getNext().QueryInterface(nsIPKCS11Module);
+    let slotnames = [];
+    let slots = module.listSlots();
+    while (slots.hasMoreElements()) {
+      let slot = slots.getNext().QueryInterface(nsIPKCS11Slot);
+      // Token names are preferred because NSS prefers lookup by token name.
+      slotnames.push(slot.tokenName ? slot.tokenName : slot.name);
+    }
+    AddModule(module.name, slotnames);
+  }
 
-  try {
-    modules.isDone();
-  } catch (e) { done = true; }
-  while (!done) {
-    var module = modules.currentItem().QueryInterface(nsIPKCS11Module);
-    if (module) {
-      var slotnames = [];
-      var slots = module.listSlots();
-      var slots_done = false;
-      try {
-        slots.isDone();
-      } catch (e) { slots_done = true; }
-      while (!slots_done) {
-        var slot = null;
-        try {
-          slot = slots.currentItem().QueryInterface(nsIPKCS11Slot);
-        } catch (e) { slot = null; }
-        // in the ongoing discussion of whether slot names or token names
-        // are to be shown, I've gone with token names because NSS will
-        // prefer lookup by token name.  However, the token may not be
-        // present, so maybe slot names should be listed, while token names
-        // are "remembered" for lookup?
-        if (slot != null) {
-          slotnames[slotnames.length] = slot.tokenName ? slot.tokenName
-                                                       : slot.name;
-        }
-        try {
-          slots.next();
-        } catch (e) { slots_done = true; }
-      }
-      AddModule(module.name, slotnames);
-    }
-    try {
-      modules.next();
-    } catch (e) { done = true; }
-  }
-  /* Set the text on the fips button */
+  // Set the text on the FIPS button.
   SetFIPSButton();
 }
 
 function SetFIPSButton()
 {
   var fipsButton = document.getElementById("fipsbutton");
   var label;
   if (secmoddb.isFIPSEnabled) {
--- a/security/manager/pki/resources/content/password.js
+++ b/security/manager/pki/resources/content/password.js
@@ -34,38 +34,34 @@ function onLoad()
       // as window name must be a subset of ascii, and the code was
       // previously trying to assign unicode to the window's name.
       // I checked all the places where we get a password prompt and
       // all of them pass an argument as part of this patch.
       tokenName = "";
   }
 
   if (tokenName == "") {
-     var sectokdb = Components.classes[nsPK11TokenDB].getService(nsIPK11TokenDB);
-     var tokenList = sectokdb.listTokens();
-     var enumElement;
-     let i = 0;
-     var menu = document.getElementById("tokenMenu");
-     try {
-        for (; !tokenList.isDone(); tokenList.next()) {
-           enumElement = tokenList.currentItem();
-           var token = enumElement.QueryInterface(nsIPK11Token);
-           if(token.needsLogin() || !(token.needsUserInit)) {
-              var menuItemNode = document.createElement("menuitem");
-              menuItemNode.setAttribute("value", token.tokenName);
-              menuItemNode.setAttribute("label", token.tokenName);
-              menu.firstChild.appendChild(menuItemNode);
-              if (i == 0) {
-                 menu.selectedItem = menuItemNode;
-                 tokenName = token.tokenName;
-              }
-              i++;
-           }
+    let tokenDB = Components.classes[nsPK11TokenDB].getService(nsIPK11TokenDB);
+    let tokenList = tokenDB.listTokens();
+    let i = 0;
+    let menu = document.getElementById("tokenMenu");
+    while (tokenList.hasMoreElements()) {
+      let token = tokenList.getNext().QueryInterface(nsIPK11Token);
+      if (token.needsLogin() || !(token.needsUserInit)) {
+        let menuItemNode = document.createElement("menuitem");
+        menuItemNode.setAttribute("value", token.tokenName);
+        menuItemNode.setAttribute("label", token.tokenName);
+        menu.firstChild.appendChild(menuItemNode);
+        if (i == 0) {
+          menu.selectedItem = menuItemNode;
+          tokenName = token.tokenName;
         }
-     } catch (exception) {}
+        i++;
+      }
+    }
   } else {
     var sel = document.getElementById("tokenMenu");
     sel.setAttribute("hidden", "true");
     var tag = document.getElementById("tokenName");
     tag.setAttribute("value", tokenName);
   }
 
   process();
--- a/security/manager/ssl/nsIPK11TokenDB.idl
+++ b/security/manager/ssl/nsIPK11TokenDB.idl
@@ -2,17 +2,17 @@
  *
  * 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/. */
 
 #include "nsISupports.idl"
 
 interface nsIPK11Token;
-interface nsIEnumerator;
+interface nsISimpleEnumerator;
 
 /**
  * The PK11 Token Database provides access to the PK11 modules
  * that are installed, and the tokens that are available.
  * Interfaces: nsIPK11TokenDB
  * Threading: ??
  */
 %{C++
@@ -33,12 +33,10 @@ interface nsIPK11TokenDB : nsISupports
   /*
    * Find a token by name
    */
   nsIPK11Token findTokenByName(in wstring tokenName);
 
   /*
    * List all tokens
    */
-  nsIEnumerator listTokens();
-
+  nsISimpleEnumerator listTokens();
 };
-
--- a/security/manager/ssl/nsIPKCS11Module.idl
+++ b/security/manager/ssl/nsIPKCS11Module.idl
@@ -2,23 +2,20 @@
  *
  * 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/. */
 
 #include "nsISupports.idl"
 
 interface nsIPKCS11Slot;
-interface nsIEnumerator;
+interface nsISimpleEnumerator;
 
 [scriptable, uuid(8a44bdf9-d1a5-4734-bd5a-34ed7fe564c2)]
 interface nsIPKCS11Module : nsISupports
 {
-
   readonly attribute wstring name;
   readonly attribute wstring libName;
 
   nsIPKCS11Slot findSlotByName(in wstring name);
 
-  nsIEnumerator listSlots();
-
+  nsISimpleEnumerator listSlots();
 };
-
--- a/security/manager/ssl/nsIPKCS11ModuleDB.idl
+++ b/security/manager/ssl/nsIPKCS11ModuleDB.idl
@@ -3,34 +3,33 @@
  * 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/. */
 
 #include "nsISupports.idl"
 
 interface nsIPKCS11Module;
 interface nsIPKCS11Slot;
-interface nsIEnumerator;
+interface nsISimpleEnumerator;
 
 %{C++
 #define NS_PKCS11MODULEDB_CONTRACTID "@mozilla.org/security/pkcs11moduledb;1"
 %}
 
 [scriptable, uuid(ff9fbcd7-9517-4334-b97a-ceed78909974)]
 interface nsIPKCS11ModuleDB : nsISupports
 {
   nsIPKCS11Module getInternal();
 
   nsIPKCS11Module getInternalFIPS();
 
   nsIPKCS11Module findModuleByName(in wstring name);
 
   nsIPKCS11Slot findSlotByName(in wstring name);
 
-  nsIEnumerator listModules();
+  nsISimpleEnumerator listModules();
 
   readonly attribute boolean canToggleFIPS;
 
   void toggleFIPSMode();
 
   readonly attribute boolean isFIPSEnabled;
 };
-
--- a/security/manager/ssl/nsPK11TokenDB.cpp
+++ b/security/manager/ssl/nsPK11TokenDB.cpp
@@ -1,23 +1,23 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsPK11TokenDB.h"
+
+#include "nsIMutableArray.h"
 #include "nsISupports.h"
-#include "nsISupportsArray.h"
-#include "nsIPK11TokenDB.h"
+#include "nsNSSComponent.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
 #include "prerror.h"
+#include "ScopedNSSTypes.h"
 #include "secerr.h"
-#include "nsReadableUtils.h"
-#include "nsNSSComponent.h"
-#include "nsServiceManagerUtils.h"
-
-#include "nsPK11TokenDB.h"
 
 extern PRLogModuleInfo* gPIPNSSLog;
 
 NS_IMPL_ISUPPORTS(nsPK11Token, nsIPK11Token)
 
 nsPK11Token::nsPK11Token(PK11SlotInfo *slot)
 {
   nsNSSShutDownPreventionLock locker;
@@ -442,42 +442,36 @@ FindTokenByName(const char16_t* tokenNam
   token = new nsPK11Token(slot);
   token.forget(_retval);
 
 done:
   if (slot) PK11_FreeSlot(slot);
   return rv;
 }
 
-NS_IMETHODIMP nsPK11TokenDB::ListTokens(nsIEnumerator* *_retval)
+NS_IMETHODIMP
+nsPK11TokenDB::ListTokens(nsISimpleEnumerator** _retval)
 {
   nsNSSShutDownPreventionLock locker;
-  nsCOMPtr<nsISupportsArray> array;
-  PK11SlotList *list = 0;
-  PK11SlotListElement *le;
+  nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (!array) {
+    return NS_ERROR_FAILURE;
+  }
 
   *_retval = nullptr;
-  nsresult rv = NS_NewISupportsArray(getter_AddRefs(array));
-  if (NS_FAILED(rv)) { goto done; }
+
+  UniquePK11SlotList list(
+    PK11_GetAllTokens(CKM_INVALID_MECHANISM, false, false, 0));
+  if (!list) {
+    return NS_ERROR_FAILURE;
+  }
 
-  /* List all tokens, creating PK11Token objects and putting them
-   * into the array.
-   */
-  list = PK11_GetAllTokens(CKM_INVALID_MECHANISM, false, false, 0);
-  if (!list) { rv = NS_ERROR_FAILURE; goto done; }
-
-  for (le = PK11_GetFirstSafe(list); le; le = PK11_GetNextSafe(list, le, false)) {
+  for (PK11SlotListElement* le = PK11_GetFirstSafe(list.get()); le;
+       le = PK11_GetNextSafe(list.get(), le, false)) {
     nsCOMPtr<nsIPK11Token> token = new nsPK11Token(le->slot);
-    rv = array->AppendElement(token);
+    nsresult rv = array->AppendElement(token, false);
     if (NS_FAILED(rv)) {
-      PK11_FreeSlotListElement(list, le);
-      rv = NS_ERROR_OUT_OF_MEMORY;
-      goto done;
+      return rv;
     }
   }
 
-  rv = array->Enumerate(_retval);
-
-done:
-  if (list) PK11_FreeSlotList(list);
-  return rv;
+  return array->Enumerate(_retval);
 }
-
--- a/security/manager/ssl/nsPKCS11Slot.cpp
+++ b/security/manager/ssl/nsPKCS11Slot.cpp
@@ -2,17 +2,17 @@
  * 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/. */
 
 #include "nsPKCS11Slot.h"
 
 #include "mozilla/Logging.h"
 #include "mozilla/Telemetry.h"
 #include "nsCOMPtr.h"
-#include "nsISupportsArray.h"
+#include "nsIMutableArray.h"
 #include "nsPK11TokenDB.h"
 #include "secmod.h"
 
 using mozilla::LogLevel;
 
 extern PRLogModuleInfo* gPIPNSSLog;
 
 NS_IMPL_ISUPPORTS(nsPKCS11Slot, nsIPKCS11Slot)
@@ -313,44 +313,45 @@ nsPKCS11Module::FindSlotByName(const cha
   } 
   free(asciiname);
   nsCOMPtr<nsIPKCS11Slot> slot = new nsPKCS11Slot(slotinfo);
   PK11_FreeSlot(slotinfo);
   slot.forget(_retval);
   return NS_OK;
 }
 
-NS_IMETHODIMP 
-nsPKCS11Module::ListSlots(nsIEnumerator **_retval)
+NS_IMETHODIMP
+nsPKCS11Module::ListSlots(nsISimpleEnumerator** _retval)
 {
   nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown())
+  if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
+  }
 
-  nsresult rv = NS_OK;
-  int i;
-  /* get isupports array */
-  nsCOMPtr<nsISupportsArray> array;
-  rv = NS_NewISupportsArray(getter_AddRefs(array));
-  if (NS_FAILED(rv)) return rv;
+  nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (!array) {
+    return NS_ERROR_FAILURE;
+  }
+
   /* applications which allow new slot creation (which Firefox now does
    * since it uses the WaitForSlotEvent call) need to hold the
    * ModuleList Read lock to prevent the slot array from changing out
    * from under it. */
-  SECMODListLock *lock = SECMOD_GetDefaultModuleListLock();
-  SECMOD_GetReadLock(lock);
-  for (i=0; i<mModule->slotCount; i++) {
+  AutoSECMODListReadLock lock;
+  for (int i = 0; i < mModule->slotCount; i++) {
     if (mModule->slots[i]) {
       nsCOMPtr<nsIPKCS11Slot> slot = new nsPKCS11Slot(mModule->slots[i]);
-      array->AppendElement(slot);
+      nsresult rv = array->AppendElement(slot, false);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
     }
   }
-  SECMOD_ReleaseReadLock(lock);
-  rv = array->Enumerate(_retval);
-  return rv;
+
+  return array->Enumerate(_retval);
 }
 
 NS_IMPL_ISUPPORTS(nsPKCS11ModuleDB, nsIPKCS11ModuleDB, nsICryptoFIPSInfo)
 
 nsPKCS11ModuleDB::nsPKCS11ModuleDB()
 {
 }
 
@@ -412,45 +413,47 @@ nsPKCS11ModuleDB::FindSlotByName(const c
   if (!slotinfo)
     return NS_ERROR_FAILURE;
   nsCOMPtr<nsIPKCS11Slot> slot = new nsPKCS11Slot(slotinfo);
   PK11_FreeSlot(slotinfo);
   slot.forget(_retval);
   return NS_OK;
 }
 
-NS_IMETHODIMP 
-nsPKCS11ModuleDB::ListModules(nsIEnumerator **_retval)
+NS_IMETHODIMP
+nsPKCS11ModuleDB::ListModules(nsISimpleEnumerator** _retval)
 {
   nsNSSShutDownPreventionLock locker;
-  nsresult rv = NS_OK;
-  /* get isupports array */
-  nsCOMPtr<nsISupportsArray> array;
-  rv = NS_NewISupportsArray(getter_AddRefs(array));
-  if (NS_FAILED(rv)) return rv;
-  /* get the default list of modules */
-  SECMODModuleList *list = SECMOD_GetDefaultModuleList();
+  nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (!array) {
+    return NS_ERROR_FAILURE;
+  }
+
   /* lock down the list for reading */
-  SECMODListLock *lock = SECMOD_GetDefaultModuleListLock();
-  SECMOD_GetReadLock(lock);
-  while (list) {
+  AutoSECMODListReadLock lock;
+  for (SECMODModuleList* list = SECMOD_GetDefaultModuleList(); list;
+       list = list->next) {
     nsCOMPtr<nsIPKCS11Module> module = new nsPKCS11Module(list->module);
-    array->AppendElement(module);
-    list = list->next;
+    nsresult rv = array->AppendElement(module, false);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
   }
+
   /* Get the modules in the database that didn't load */
-  list = SECMOD_GetDeadModuleList();
-  while (list) {
+  for (SECMODModuleList* list = SECMOD_GetDeadModuleList(); list;
+       list = list->next) {
     nsCOMPtr<nsIPKCS11Module> module = new nsPKCS11Module(list->module);
-    array->AppendElement(module);
-    list = list->next;
+    nsresult rv = array->AppendElement(module, false);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
   }
-  SECMOD_ReleaseReadLock(lock);
-  rv = array->Enumerate(_retval);
-  return rv;
+
+  return array->Enumerate(_retval);
 }
 
 NS_IMETHODIMP nsPKCS11ModuleDB::GetCanToggleFIPS(bool *aCanToggleFIPS)
 {
   nsNSSShutDownPreventionLock locker;
   *aCanToggleFIPS = SECMOD_CanDeleteInternalModule();
   return NS_OK;
 }
--- a/security/manager/ssl/nsPKCS11Slot.h
+++ b/security/manager/ssl/nsPKCS11Slot.h
@@ -1,25 +1,25 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef __NS_PKCS11SLOT_H__
-#define __NS_PKCS11SLOT_H__
+#ifndef nsPKCS11Slot_h
+#define nsPKCS11Slot_h
 
-#include "nsISupports.h"
-#include "nsIPKCS11Slot.h"
+#include "nsICryptoFIPSInfo.h"
 #include "nsIPKCS11Module.h"
 #include "nsIPKCS11ModuleDB.h"
-#include "nsICryptoFIPSInfo.h"
+#include "nsIPKCS11Slot.h"
+#include "nsISupports.h"
+#include "nsNSSShutDown.h"
 #include "nsString.h"
 #include "pk11func.h"
-#include "nsNSSShutDown.h"
 
 class nsPKCS11Slot : public nsIPKCS11Slot,
                      public nsNSSShutDownObject
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPKCS11SLOT
 
@@ -72,9 +72,28 @@ protected:
   virtual ~nsPKCS11ModuleDB();
   /* additional members */
 };
 
 #define NS_PKCS11MODULEDB_CID \
 { 0xff9fbcd7, 0x9517, 0x4334, \
   { 0xb9, 0x7a, 0xce, 0xed, 0x78, 0x90, 0x99, 0x74 }}
 
-#endif
+class MOZ_RAII AutoSECMODListReadLock final
+{
+public:
+  AutoSECMODListReadLock()
+    : mLock(SECMOD_GetDefaultModuleListLock())
+  {
+    MOZ_ASSERT(mLock, "Should have the default SECMOD lock");
+    SECMOD_GetReadLock(mLock);
+  }
+
+  ~AutoSECMODListReadLock()
+  {
+    SECMOD_ReleaseReadLock(mLock);
+  }
+
+private:
+  SECMODListLock* mLock;
+};
+
+#endif // nsPKCS11Slot_h
--- a/security/manager/ssl/tests/unit/test_pkcs11_insert_remove.js
+++ b/security/manager/ssl/tests/unit/test_pkcs11_insert_remove.js
@@ -11,26 +11,48 @@
 // has been succssfully observed, and then it unloads the test module.
 
 // Ensure that the appropriate initialization has happened.
 do_get_profile();
 Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
 
 const gExpectedTokenLabel = "Test PKCS11 TokeƱ Label";
 
+const gTokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
+                   .getService(Ci.nsIPK11TokenDB);
+
 function SmartcardObserver(type) {
   this.type = type;
   do_test_pending();
 }
 
 SmartcardObserver.prototype = {
   observe: function(subject, topic, data) {
     equal(topic, this.type, "Observed and expected types should match");
     equal(gExpectedTokenLabel, data,
           "Expected and observed token labels should match");
+
+    // Test that the token list contains the test token only when the test
+    // module is loaded.
+    // Note: This test is located here out of convenience. In particular,
+    //       observing the "smartcard-insert" event is the only time where it
+    //       is reasonably certain for the test token to be present (see the top
+    //       level comment for this file for why).
+    let tokenList = gTokenDB.listTokens();
+    let testTokenLabelFound = false;
+    while (tokenList.hasMoreElements()) {
+      let token = tokenList.getNext().QueryInterface(Ci.nsIPK11Token);
+      if (token.tokenLabel == gExpectedTokenLabel) {
+        testTokenLabelFound = true;
+        break;
+      }
+    }
+    equal(testTokenLabelFound, this.type == "smartcard-insert",
+          "Should find test token only when the test module is loaded");
+
     Services.obs.removeObserver(this, this.type);
     do_test_finished();
   }
 };
 
 function run_test() {
   Services.obs.addObserver(new SmartcardObserver("smartcard-insert"),
                            "smartcard-insert", false);
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_pkcs11_list.js
@@ -0,0 +1,94 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+"use strict";
+
+// Tests the methods for listing PKCS #11 modules and slots via loading and
+// unloading a test PKCS #11 module.
+
+// Note: Tests for listing PKCS #11 tokens are located in
+//       test_pkcs11_insert_remove.js out of convenience.
+
+// Ensure that the appropriate initialization has happened.
+do_get_profile();
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+const gModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"]
+                    .getService(Ci.nsIPKCS11ModuleDB);
+
+function checkTestModuleNotPresent() {
+  let modules = gModuleDB.listModules();
+  ok(modules.hasMoreElements(),
+     "One or more modules should be present with test module not present");
+  while (modules.hasMoreElements()) {
+    let module = modules.getNext().QueryInterface(Ci.nsIPKCS11Module);
+    notEqual(module.name, "PKCS11 Test Module",
+             "Non-test module name shouldn't equal 'PKCS11 Test Module'");
+  }
+}
+
+/**
+ * Checks that the test module exists in the module list.
+ *
+ * @returns {nsIPKCS11Module}
+ *          The test module.
+ */
+function checkTestModuleExists() {
+  let modules = gModuleDB.listModules();
+  ok(modules.hasMoreElements(),
+     "One or more modules should be present with test module present");
+  let testModule = null;
+  while (modules.hasMoreElements()) {
+    let module = modules.getNext().QueryInterface(Ci.nsIPKCS11Module);
+    if (module.name == "PKCS11 Test Module") {
+      testModule = module;
+      break;
+    }
+  }
+  notEqual(testModule, null, "Test module should have been found");
+
+  return testModule;
+}
+
+function run_test() {
+  let libraryName = ctypes.libraryName("pkcs11testmodule");
+  let libraryFile = Services.dirsvc.get("CurWorkD", Ci.nsILocalFile);
+  libraryFile.append("pkcs11testmodule");
+  libraryFile.append(libraryName);
+  ok(libraryFile.exists(), "The pkcs11testmodule file should exist");
+
+  // Check that if we have never added the test module, that we don't find it
+  // in the module list.
+  checkTestModuleNotPresent();
+
+  // Check that adding the test module makes it appear in the module list.
+  let pkcs11 = Cc["@mozilla.org/security/pkcs11;1"].getService(Ci.nsIPKCS11);
+  do_register_cleanup(() => {
+    try {
+      pkcs11.deleteModule("PKCS11 Test Module");
+    } catch (e) {
+      // deleteModule() throws if the module we tell it to delete is missing,
+      // or if some other thing went wrong. Since we're just cleaning up,
+      // there's nothing to do even if the call fails. In addition, we delete
+      // the test module during a normal run of this test file, so we need to
+      // catch the exception that is raised to not have the test fail.
+    }
+  });
+  pkcs11.addModule("PKCS11 Test Module", libraryFile.path, 0, 0);
+  let testModule = checkTestModuleExists();
+
+  // Check that listing the slots for the test module works.
+  let slots = testModule.listSlots();
+  let testModuleSlotCount = 0;
+  while (slots.hasMoreElements()) {
+    let slot = slots.getNext().QueryInterface(Ci.nsIPKCS11Slot);
+    equal(slot.name, "Test PKCS11 Slot",
+          "Test module slot should have correct name");
+    testModuleSlotCount++;
+  }
+  equal(testModuleSlotCount, 1, "Test module should only have one slot");
+
+  // Check that deleting the test module makes it disappear from the module list.
+  pkcs11.deleteModule("PKCS11 Test Module");
+  checkTestModuleNotPresent();
+}
--- a/security/manager/ssl/tests/unit/xpcshell-smartcards.ini
+++ b/security/manager/ssl/tests/unit/xpcshell-smartcards.ini
@@ -3,14 +3,17 @@ head = head_psm.js
 tail =
 tags = psm
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 support-files =
 
 [test_pkcs11_insert_remove.js]
 # Bug 1049969: this test doesn't work on windows
 skip-if = os == "win"
+[test_pkcs11_list.js]
+# Bug 1049969: this test doesn't work on windows
+skip-if = os == "win"
 [test_pkcs11_no_events_after_removal.js]
 # Bug 1049969: this test doesn't work on windows
 skip-if = os == "win"
 [test_pkcs11_safe_mode.js]
 # Bug 1049969: this test doesn't work on windows
 skip-if = os == "win"