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

Commit

Permalink
feat(patch): support requestAnimationFrame time loops
Browse files Browse the repository at this point in the history
- Add custom patching function for repeating callback functions to
support requestAnimationFrame wrapping.
- Bind requestAnimationFrame callback functions to the current zone
unless it is the root zone.
- DRY up requestAnimationFrame spec file (to remove duplication across
browsers)
- Add test that verifies that when registering frame callbacks from
within a frame callback, the zone in which they execute remains
constant.
- Add test to verify the wrapper is tolerant of invalid arguments.
- Add note to readme about behavior of raf
  • Loading branch information
justindujardin authored and vicb committed Aug 27, 2015
1 parent c7a2ed9 commit 3d6dc08
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 50 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,14 @@ For instance `clearTimeout` and `removeEventListener`.
These hooks allow you to change the behavior of `window.setTimeout`, `window.setInterval`, etc.
While in this zone, calls to `window.setTimeout` will redirect to `zone.setTimeout`.

### `zone.requestAnimationFrame`, `zone.webkitRequestAnimationFrame`, `zone.mozRequestAnimationFrame`

These hooks allow you to change the behavior of `window.requestAnimationFrame()`,
`window.webkitRequestAnimationFrame`, and `window.mozRequestAnimationFrame`.

By default the callback is executed in the zone where those methods have been called to avoid
growing the stack size on each recursive call.

### `zone.addEventListener`

This hook allows you to intercept calls to `EventTarget.addEventListener`.
Expand Down
2 changes: 1 addition & 1 deletion lib/patch/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function apply() {
'immediate'
]);

fnPatch.patchSetFunction(global, [
fnPatch.patchRequestAnimationFrame(global, [
'requestAnimationFrame',
'mozRequestAnimationFrame',
'webkitRequestAnimationFrame'
Expand Down
28 changes: 28 additions & 0 deletions lib/patch/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,33 @@ function patchSetClearFunction(obj, fnNames) {
});
};


/**
* requestAnimationFrame is typically recursively called from within the callback function
* that it executes. To handle this case, only fork a zone if this is executed
* within the root zone.
*/
function patchRequestAnimationFrame(obj, fnNames) {
fnNames.forEach(function (name) {
var delegate = obj[name];
if (delegate) {
global.zone[name] = function (fn) {
var callZone = global.zone.isRootZone() ? global.zone.fork() : global.zone;
if (fn) {
arguments[0] = function () {
return callZone.run(fn, arguments);
};
}
return delegate.apply(obj, arguments);
};

obj[name] = function () {
return global.zone[name].apply(this, arguments);
};
}
});
};

function patchSetFunction(obj, fnNames) {
fnNames.forEach(function (name) {
var delegate = obj[name];
Expand Down Expand Up @@ -86,5 +113,6 @@ function patchFunction(obj, fnNames) {
module.exports = {
patchSetClearFunction: patchSetClearFunction,
patchSetFunction: patchSetFunction,
patchRequestAnimationFrame: patchRequestAnimationFrame,
patchFunction: patchFunction
};
76 changes: 27 additions & 49 deletions test/patch/requestAnimationFrame.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,41 @@
describe('requestAnimationFrame', function () {
var testZone = zone.fork();

describe('requestAnimationFrame', function () {
it('should run the passed callback in a zone', function (done) {
testZone.run(function() {
if (typeof requestAnimationFrame === 'undefined') {
console.log('WARNING: skipping requestAnimationFrame test (missing this API)');
return done();
}
var functions = [
'requestAnimationFrame',
'webkitRequestAnimationFrame',
'mozRequestAnimationFrame'
];

// Some browsers (especially Safari) do not fire requestAnimationFrame
// if they are offscreen. We can disable this test for those browsers and
// assume the patch works if setTimeout works, since they are mechanically
// the same
requestAnimationFrame(function () {
expect(zone).toBeDirectChildOf(testZone);
done();
});
});
});
});
functions.forEach(function (fnName) {
describe(fnName, ifEnvSupports(fnName, function () {
var rAF = window[fnName];

describe('mozRequestAnimationFrame', function () {
it('should run the passed callback in a zone', function (done) {
testZone.run(function() {
if (typeof mozRequestAnimationFrame === 'undefined') {
console.log('WARNING: skipping mozRequestAnimationFrame test (missing this API)');
return done();
}

// Some browsers (especially Safari) do not fire mozRequestAnimationFrame
// if they are offscreen. We can disable this test for those browsers and
// assume the patch works if setTimeout works, since they are mechanically
// the same
mozRequestAnimationFrame(function () {
expect(zone).toBeDirectChildOf(testZone);
it('should be tolerant of invalid arguments', function (done) {
testZone.run(function () {
// rAF throws an error on invalid arguments, so expect that.
expect(function () {
rAF(null);
}).toThrow();
done();
});
});
});
});

describe('webkitRequestAnimationFrame', function () {
it('should run the passed callback in a zone', function (done) {
testZone.run(function() {
if (typeof webkitRequestAnimationFrame === 'undefined') {
console.log('WARNING: skipping webkitRequestAnimationFrame test (missing this API)');
return done();
}
it('should bind to same zone when called recursively', function (done) {
testZone.run(function () {
var frames = 0;

// Some browsers (especially Safari) do not fire webkitRequestAnimationFrame
// if they are offscreen. We can disable this test for those browsers and
// assume the patch works if setTimeout works, since they are mechanically
// the same
webkitRequestAnimationFrame(function () {
expect(zone).toBeDirectChildOf(testZone);
done();
function frameCallback() {
expect(zone === testZone).toBe(true);
if (frames++ > 15) {
return done();
}
rAF(frameCallback);
}

rAF(frameCallback);
});
});
});
}));
});
});

0 comments on commit 3d6dc08

Please sign in to comment.