diff --git a/resources/static/common/js/modules/xhr_disable_form.js b/resources/static/common/js/modules/xhr_disable_form.js
index 23ba3ebed9c47d3b571abb7db95704859163e85f..db96d112602f866baea4bf43c655fc672ae75e58 100644
--- a/resources/static/common/js/modules/xhr_disable_form.js
+++ b/resources/static/common/js/modules/xhr_disable_form.js
@@ -11,12 +11,40 @@ BrowserID.Modules.XHRDisableForm = (function() {
 
   var Module = bid.Modules.PageModule.extend({
     start: function(options) {
-      var self=this;
-
-      self.subscribe("xhr_start",
-        dom.addClass.curry("body", "submit_disabled"));
-      self.subscribe("xhr_complete",
-        dom.removeClass.curry("body", "submit_disabled"));
+      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_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
+
+        // 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");
+        }, 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 b3152c6eb98da4efa203cddfd2b1efcd2571dcc1..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() {
@@ -29,13 +29,50 @@
     }
   });
 
-  test("xhr_start adds 'submit_disabled' to class, xhr_complete removes it", function() {
+  asyncTest("xhr_start adds 'submit_disabled' to class, xhr_complete removes it", function() {
     var body = $("body");
 
     mediator.publish("xhr_start");
     equal(body.hasClass("submit_disabled"), true, "xhr_start adds submit_disabled");
 
+    // submit_disabled is removed after a small delay so that if consecutive
+    // XHR requests happen, there is no button flicker. See issue #1898
+    // - https://github.com/mozilla/browserid/issues/1898
+    mediator.subscribe("submit_enabled", function() {
+      equal(body.hasClass("submit_disabled"), false, "xhr_complete removes submit_disabled");
+      start();
+    });
     mediator.publish("xhr_complete");
-    equal(body.hasClass("submit_disabled"), false, "xhr_complete removes submit_disabled");
   });
+
+  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);
+  });
+
 }());