diff --git a/apps/email/js/cards.js b/apps/email/js/cards.js index 63096f72a46e..c91ff961f2d6 100644 --- a/apps/email/js/cards.js +++ b/apps/email/js/cards.js @@ -6,6 +6,7 @@ var cardsInit = require('cards_init'), mozL10n = require('l10n!'), evt = require('evt'), toaster = require('toaster'), + transitionEnd = require('transition_end'), hookupInputAreaResetButtons = require('input_areas'); function addClass(domNode, name) { @@ -121,9 +122,7 @@ var cards = { // XXX be more platform detecty. or just add more events. unless the // prefixes are already gone with webkit and opera? - this._cardsNode.addEventListener('transitionend', - this._onTransitionEnd.bind(this), - false); + transitionEnd(this._cardsNode, this._onTransitionEnd.bind(this), false); // Listen for visibility changes to let current card know of them too. // Do this here instead of each card needing to listen, and needing to know diff --git a/apps/email/js/cards/folder_picker.js b/apps/email/js/cards/folder_picker.js index 287dd2cc2de6..de2d6c95e0bc 100644 --- a/apps/email/js/cards/folder_picker.js +++ b/apps/email/js/cards/folder_picker.js @@ -7,7 +7,8 @@ var fldFolderItemNode = require('tmpl!./fld/folder_item.html'), FOLDER_DEPTH_CLASSES = require('folder_depth_classes'), cards = require('cards'), model = require('model'), - evt = require('evt'); + evt = require('evt'), + transitionEnd = require('transition_end'); require('css!style/folder_cards'); @@ -24,7 +25,7 @@ return [ this.bindContainerHandler(this.accountListContainer, 'click', this.onClickAccount.bind(this)); - this.addEventListener('transitionend', this.onTransitionEnd.bind(this)); + transitionEnd(this, this.onTransitionEnd.bind(this)); // If more than one account, need to show the account dropdown var accountCount = model.getAccountCount(); diff --git a/apps/email/js/message_list_topbar.js b/apps/email/js/message_list_topbar.js index 2a99a6b7250c..a5ddccd848ec 100644 --- a/apps/email/js/message_list_topbar.js +++ b/apps/email/js/message_list_topbar.js @@ -40,7 +40,8 @@ * */ define(function(require, exports, module) { - var mozL10n = require('l10n!'); + var mozL10n = require('l10n!'), + transitionEnd = require('transition_end'); var proto = { domNode: null, @@ -55,8 +56,7 @@ define(function(require, exports, module) { createdCallback: function() { this.domNode.addEventListener('click', this._onClick.bind(this)); - this.domNode.addEventListener('transitionend', - this._onTransitionEnd.bind(this)); + transitionEnd(this.domNode, this._onTransitionEnd.bind(this)); }, /** diff --git a/apps/email/js/toaster.js b/apps/email/js/toaster.js index bee8a2850c74..faa223ec0604 100644 --- a/apps/email/js/toaster.js +++ b/apps/email/js/toaster.js @@ -2,6 +2,7 @@ define(function(require) { var mozL10n = require('l10n!'); var toasterNode = require('tmpl!./cards/toaster.html'); + var transitionEnd = require('transition_end'); /** * Manages the display of short status notifications, or 'toasts'. @@ -37,7 +38,7 @@ define(function(require) { this.actionButton = this.el.querySelector('.toaster-action'); this.el.addEventListener('click', this.hide.bind(this)); - this.el.addEventListener('transitionend', this.hide.bind(this)); + transitionEnd(this.el, this.hide.bind(this)); // The target is used for the action to allow a larger tap target than // just the button. diff --git a/apps/email/js/transition_end.js b/apps/email/js/transition_end.js new file mode 100644 index 000000000000..eebe077f7600 --- /dev/null +++ b/apps/email/js/transition_end.js @@ -0,0 +1,28 @@ +'use strict'; + +/** + * transitionend is not guaranteed to be async after a layout recalulation. + * Specifically, a clientHeight read might trigger sync dispatch of + * transitionend event to listeners, which could end up calling the function + * that did the clientHeight, but the first call would still be stuck waiting + * on the clientHeight read to finish. The issue is tracked in bug 1135960. This + * is a workaround for that, to guarantee async dispatch by using the async + * nature of promise .then callbacks. + */ +define(function(require, exports, module) { + return function transitionEnd(node, fn, capturing) { + function asyncFn(event) { + Promise.resolve().then(function() { + fn(event); + }) + .catch(function(error) { + console.error(error); + }); + } + node.addEventListener('transitionend', asyncFn, capturing); + + // Return the function used with addEventListener to allow the caller of + // this helper to later call removeEventListener. + return asyncFn; + }; +});