diff --git a/src/ng/animateRunner.js b/src/ng/animateRunner.js index 51701b4c16f8..0b6cacc8d6f0 100644 --- a/src/ng/animateRunner.js +++ b/src/ng/animateRunner.js @@ -28,8 +28,8 @@ var $$AnimateAsyncRunFactoryProvider = function() { }; var $$AnimateRunnerFactoryProvider = function() { - this.$get = ['$q', '$sniffer', '$$animateAsyncRun', - function($q, $sniffer, $$animateAsyncRun) { + this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$document', '$timeout', + function($q, $sniffer, $$animateAsyncRun, $document, $timeout) { var INITIAL_STATE = 0; var DONE_PENDING_STATE = 1; @@ -74,8 +74,19 @@ var $$AnimateRunnerFactoryProvider = function() { function AnimateRunner(host) { this.setHost(host); + var rafTick = $$animateAsyncRun(); + var timeoutTick = function(fn) { + $timeout(fn, 0, false); + }; + this._doneCallbacks = []; - this._runInAnimationFrame = $$animateAsyncRun(); + this._tick = function(fn) { + if ($document[0].hidden) { + timeoutTick(fn); + } else { + rafTick(fn); + } + }; this._state = 0; } @@ -148,7 +159,7 @@ var $$AnimateRunnerFactoryProvider = function() { var self = this; if (self._state === INITIAL_STATE) { self._state = DONE_PENDING_STATE; - self._runInAnimationFrame(function() { + self._tick(function() { self._resolve(response); }); } diff --git a/src/ngAnimate/animateQueue.js b/src/ngAnimate/animateQueue.js index 6432f80936c6..39669a99491b 100644 --- a/src/ngAnimate/animateQueue.js +++ b/src/ngAnimate/animateQueue.js @@ -337,7 +337,8 @@ var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) { // this is a hard disable of all animations for the application or on // the element itself, therefore there is no need to continue further // past this point if not enabled - var skipAnimations = !animationsEnabled || disabledElementsLookup.get(node); + var doc = $document[0]; + var skipAnimations = !animationsEnabled || doc.hidden || disabledElementsLookup.get(node); var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {}; var hasExistingAnimation = !!existingAnimation.state; diff --git a/test/ng/animateRunnerSpec.js b/test/ng/animateRunnerSpec.js index d6fab470e8df..f8bcf0684579 100644 --- a/test/ng/animateRunnerSpec.js +++ b/test/ng/animateRunnerSpec.js @@ -162,6 +162,43 @@ describe("$$AnimateRunner", function() { expect(animationFailed).toBe(true); })); + it("should revert to using timeouts when the webpage is not visible", function() { + var doc; + + module(function($provide) { + doc = jqLite({ + body: document.body, + hidden: true + }); + $provide.value('$document', doc); + }); + + inject(function($$AnimateRunner, $rootScope, $$rAF, $timeout) { + var spy = jasmine.createSpy(); + var runner = new $$AnimateRunner(); + runner.done(spy); + runner.complete(true); + expect(spy).not.toHaveBeenCalled(); + $$rAF.flush(); + expect(spy).not.toHaveBeenCalled(); + $timeout.flush(); + expect(spy).toHaveBeenCalled(); + + doc[0].hidden = false; + + spy = jasmine.createSpy(); + runner = new $$AnimateRunner(); + runner.done(spy); + runner.complete(true); + expect(spy).not.toHaveBeenCalled(); + $$rAF.flush(); + expect(spy).toHaveBeenCalled(); + expect(function() { + $timeout.flush(); + }).toThrow(); + }); + }); + they("should expose the `finally` promise function to handle the final state when $prop", { 'rejected': 'cancel', 'resolved': 'end' }, function(method) { inject(function($$AnimateRunner, $rootScope) { diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index 09e78af4c132..a68297e984fb 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -148,6 +148,31 @@ describe("animations", function() { expect(copiedOptions).toEqual(initialOptions); })); + it("should skip animations entirely if the document is not active", function() { + var doc; + + module(function($provide) { + doc = jqLite({ + body: document.body, + hidden: true + }); + $provide.value('$document', doc); + }); + + inject(function($animate, $rootScope) { + $animate.enter(element, parent); + $rootScope.$digest(); + expect(capturedAnimation).toBeFalsy(); + expect(element[0].parentNode).toEqual(parent[0]); + + doc[0].hidden = false; + + $animate.leave(element); + $rootScope.$digest(); + expect(capturedAnimation).toBeTruthy(); + }); + }); + it('should animate only the specified CSS className matched within $animateProvider.classNameFilter', function() { module(function($animateProvider) { $animateProvider.classNameFilter(/only-allow-this-animation/);