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); + }); + }());