Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

fix(ngAnimate): only trigger animations if the document is not hidden #13776

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/ng/animateRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
});
}
Expand Down
3 changes: 2 additions & 1 deletion src/ngAnimate/animateQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
37 changes: 37 additions & 0 deletions test/ng/animateRunnerSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
25 changes: 25 additions & 0 deletions test/ngAnimate/animateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
Expand Down