diff --git a/src/ng/raf.js b/src/ng/raf.js index a487b006f606..e0cd2796a02e 100644 --- a/src/ng/raf.js +++ b/src/ng/raf.js @@ -1,6 +1,10 @@ 'use strict'; function $$RAFProvider() { //rAF + + var provider = this; + provider.$$bufferLimit = 10; + this.$get = ['$window', '$timeout', function($window, $timeout) { var requestAnimationFrame = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame; @@ -26,42 +30,73 @@ function $$RAFProvider() { //rAF queueFn.supported = rafSupported; - var cancelLastRAF; - var taskCount = 0; - var taskQueue = []; - return queueFn; - - function flush() { - for (var i = 0; i < taskQueue.length; i++) { - var task = taskQueue[i]; - if (task) { - taskQueue[i] = null; - task(); - } - } - taskCount = taskQueue.length = 0; + function RAFTaskQueue(fn) { + this.queue = []; + this.count = 0; + this.afterFlushFn = fn; } - function queueFn(asyncFn) { - var index = taskQueue.length; + RAFTaskQueue.prototype = { + push: function(fn) { + var self = this; - taskCount++; - taskQueue.push(asyncFn); + self.queue.push(fn); + self.count++; - if (index === 0) { - cancelLastRAF = rafFn(flush); + self.rafWait(function() { + self.flush(); + }); + }, + remove: function(index) { + if (this.queue[index] !== noop) { + this.queue[index] = noop; + if (--this.count === 0) { + this.reset(); + this.flush(); + } + } + }, + reset:function() { + (this.cancelRaf || noop)(); + this.count = this.queue.length = 0; + }, + rafWait: function(fn) { + var self = this; + if (!self.cancelRaf) { + self.cancelRaf = rafFn(function() { + self.cancelRaf = null; + fn(); + }); + } + }, + flush: function() { + for (var i = 0; i < this.queue.length; i++) { + this.queue[i](); + } + this.count = this.queue.length = 0; + this.afterFlushFn(this); } + }; + + var tasks = []; + return queueFn; + function queueFn(fn) { + var lastTask = tasks.length && tasks[tasks.length - 1]; + if (!lastTask || lastTask.count === provider.$$bufferLimit) { + lastTask = tasks[tasks.length] = new RAFTaskQueue(function(self) { + var taskIndex = tasks.indexOf(self); + if (taskIndex >= 0) { + tasks.splice(taskIndex, 1); + } + }); + } + lastTask.push(fn); + var index = lastTask.count - 1; return function cancelQueueFn() { if (index >= 0) { - taskQueue[index] = null; + lastTask.remove(index); index = null; - - if (--taskCount === 0 && cancelLastRAF) { - cancelLastRAF(); - cancelLastRAF = null; - taskQueue.length = 0; - } } }; } diff --git a/test/ng/rafSpec.js b/test/ng/rafSpec.js index e700d392aae4..154d5496a4c8 100644 --- a/test/ng/rafSpec.js +++ b/test/ng/rafSpec.js @@ -71,6 +71,64 @@ describe('$$rAF', function() { expect(rafLog.length).toBe(2); })); + it('should buffer repeated calls to RAF into segments denoted by a limit which is placed on the provider', inject(function($$rAF) { + if (!$$rAF.supported) return; + + var BUFFER_LIMIT = 5; + + //we need to create our own injector to work around the ngMock overrides + var rafLog = []; + var injector = createInjector(['ng', function($provide, $$rAFProvider) { + $$rAFProvider.$$bufferLimit = BUFFER_LIMIT; + $provide.value('$window', { + location: window.location, + history: window.history, + webkitCancelAnimationFrame: noop, + webkitRequestAnimationFrame: function(fn) { + rafLog.push(fn); + } + }); + }]); + + $$rAF = injector.get('$$rAF'); + + var log = []; + function logFn() { + log.push(log.length); + } + + for (var i = 0; i < 8; i++) { + $$rAF(logFn); + } + + expect(log).toEqual([]); + expect(rafLog.length).toBe(2); + + $$rAF(logFn); + expect(rafLog.length).toBe(2); + + $$rAF(logFn); + expect(rafLog.length).toBe(2); + + $$rAF(logFn); + expect(rafLog.length).toBe(3); + + rafLog.shift()(); + expect(log).toEqual([0,1,2,3,4]); + + rafLog.shift()(); + expect(log).toEqual([0,1,2,3,4,5,6,7,8,9]); + + rafLog.shift()(); + expect(log).toEqual([0,1,2,3,4,5,6,7,8,9,10]); + + expect(rafLog.length).toBe(0); + })); + + it('should set a default buffer limit of 10', module(function($$rAF) { + expect($$rAF.$$bufferLimit).toBe(10); + })); + describe('$timeout fallback', function() { it("it should use a $timeout incase native rAF isn't suppored", function() { var timeoutSpy = jasmine.createSpy('callback');