diff --git a/resources/static/common/js/modules/xhr_disable_form.js b/resources/static/common/js/modules/xhr_disable_form.js
index 0d7f943b41cd084099137c26f3b43fea041b5368..db96d112602f866baea4bf43c655fc672ae75e58 100644
--- a/resources/static/common/js/modules/xhr_disable_form.js
+++ b/resources/static/common/js/modules/xhr_disable_form.js
@@ -11,20 +11,39 @@ BrowserID.Modules.XHRDisableForm = (function() {
 
   var Module = bid.Modules.PageModule.extend({
     start: function(options) {
-      var self=this;
+      var self=this,
+          enableDelayMS = options.enableDelayMS || 100;
+
+      function cancelRemoveClassDelay() {
+        if (self.enableDelay) {
+          clearTimeout(self.enableDelay);
+          self.enableDelay = null;
+        }
+      }
+
+      self.subscribe("xhr_start", function() {
+        // A new XHR request has started since the enableDelay was
+        // started. Since the timeout has not yet completed, cancel it so the
+        // button does not flicker.
+        cancelRemoveClassDelay();
+        dom.addClass("body", "submit_disabled");
+      });
 
-      self.subscribe("xhr_start",
-        dom.addClass.curry("body", "submit_disabled"));
       self.subscribe("xhr_complete", function() {
         // Add a small delay between the time the XHR is complete and when the
         // submit_disabled class is actually removed.  This helps reduce the
         // amount of flicker the user sees if one XHR request completes and
         // another one starts immediately afterwards.
         // See https://github.com/mozilla/browserid/issues/1898
-        setTimeout(function() {
+
+        // If multiple xhr_completes come in, the class should be removed after
+        // the timeout of the LAST completion. Cancel any that are outstanding.
+        cancelRemoveClassDelay();
+        self.enableDelay = setTimeout(function() {
           dom.removeClass("body", "submit_disabled");
+          self.enableDelay = null;
           self.publish("submit_enabled");
-        }, 100);
+        }, enableDelayMS);
       });
 
       sc.start.call(self, options);
diff --git a/resources/static/test/cases/common/js/modules/xhr_disable_form.js b/resources/static/test/cases/common/js/modules/xhr_disable_form.js
index 71efa49c4dde6ce1e93a87604b0460caf398e704..2f88ae869803be7187063191bd5fe91b58787aa0 100644
--- a/resources/static/test/cases/common/js/modules/xhr_disable_form.js
+++ b/resources/static/test/cases/common/js/modules/xhr_disable_form.js
@@ -18,10 +18,10 @@
     return mod;
   }
 
-  module("shared/modules/xhr_disable_form", {
+  module("common/js/modules/xhr_disable_form", {
     setup: function() {
       testHelpers.setup();
-      createModule();
+      createModule({ enableDelayMS: 10 });
     },
 
     teardown: function() {
@@ -44,4 +44,35 @@
     });
     mediator.publish("xhr_complete");
   });
+
+  asyncTest("multiple xhr_completes only cause one submit_enabled", function() {
+    var submitEnabledCount = 0;
+    mediator.subscribe("submit_enabled", function() {
+      submitEnabledCount++;
+    });
+    mediator.publish("xhr_complete");
+    mediator.publish("xhr_complete");
+
+    // give plenty of time to allow all submit_enabled timeouts to occur.
+    setTimeout(function() {
+      equal(submitEnabledCount, 1, "submit_enabled called only once");
+      start();
+    }, 50);
+  });
+
+  asyncTest("xhr_start after xhr_complete but before submit_enabled cancels submit_enabled", function() {
+    var submitEnabledCount = 0;
+    mediator.subscribe("submit_enabled", function() {
+      submitEnabledCount++;
+    });
+    mediator.publish("xhr_complete");
+    mediator.publish("xhr_start");
+
+    // give plenty of time to allow all submit_enabled timeouts to occur.
+    setTimeout(function() {
+      equal(submitEnabledCount, 0, "submit_enabled cancelled after xhr_start");
+      start();
+    }, 50);
+  });
+
 }());