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

Commit 3d6dc08

Browse files
justindujardinvicb
authored andcommitted
feat(patch): support requestAnimationFrame time loops
- 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
1 parent c7a2ed9 commit 3d6dc08

4 files changed

Lines changed: 64 additions & 50 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,14 @@ For instance `clearTimeout` and `removeEventListener`.
232232
These hooks allow you to change the behavior of `window.setTimeout`, `window.setInterval`, etc.
233233
While in this zone, calls to `window.setTimeout` will redirect to `zone.setTimeout`.
234234

235+
### `zone.requestAnimationFrame`, `zone.webkitRequestAnimationFrame`, `zone.mozRequestAnimationFrame`
236+
237+
These hooks allow you to change the behavior of `window.requestAnimationFrame()`,
238+
`window.webkitRequestAnimationFrame`, and `window.mozRequestAnimationFrame`.
239+
240+
By default the callback is executed in the zone where those methods have been called to avoid
241+
growing the stack size on each recursive call.
242+
235243
### `zone.addEventListener`
236244

237245
This hook allows you to intercept calls to `EventTarget.addEventListener`.

lib/patch/browser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function apply() {
1717
'immediate'
1818
]);
1919

20-
fnPatch.patchSetFunction(global, [
20+
fnPatch.patchRequestAnimationFrame(global, [
2121
'requestAnimationFrame',
2222
'mozRequestAnimationFrame',
2323
'webkitRequestAnimationFrame'

lib/patch/functions.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,33 @@ function patchSetClearFunction(obj, fnNames) {
4848
});
4949
};
5050

51+
52+
/**
53+
* requestAnimationFrame is typically recursively called from within the callback function
54+
* that it executes. To handle this case, only fork a zone if this is executed
55+
* within the root zone.
56+
*/
57+
function patchRequestAnimationFrame(obj, fnNames) {
58+
fnNames.forEach(function (name) {
59+
var delegate = obj[name];
60+
if (delegate) {
61+
global.zone[name] = function (fn) {
62+
var callZone = global.zone.isRootZone() ? global.zone.fork() : global.zone;
63+
if (fn) {
64+
arguments[0] = function () {
65+
return callZone.run(fn, arguments);
66+
};
67+
}
68+
return delegate.apply(obj, arguments);
69+
};
70+
71+
obj[name] = function () {
72+
return global.zone[name].apply(this, arguments);
73+
};
74+
}
75+
});
76+
};
77+
5178
function patchSetFunction(obj, fnNames) {
5279
fnNames.forEach(function (name) {
5380
var delegate = obj[name];
@@ -86,5 +113,6 @@ function patchFunction(obj, fnNames) {
86113
module.exports = {
87114
patchSetClearFunction: patchSetClearFunction,
88115
patchSetFunction: patchSetFunction,
116+
patchRequestAnimationFrame: patchRequestAnimationFrame,
89117
patchFunction: patchFunction
90118
};

test/patch/requestAnimationFrame.spec.js

Lines changed: 27 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,41 @@
33
describe('requestAnimationFrame', function () {
44
var testZone = zone.fork();
55

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

14-
// Some browsers (especially Safari) do not fire requestAnimationFrame
15-
// if they are offscreen. We can disable this test for those browsers and
16-
// assume the patch works if setTimeout works, since they are mechanically
17-
// the same
18-
requestAnimationFrame(function () {
19-
expect(zone).toBeDirectChildOf(testZone);
20-
done();
21-
});
22-
});
23-
});
24-
});
12+
functions.forEach(function (fnName) {
13+
describe(fnName, ifEnvSupports(fnName, function () {
14+
var rAF = window[fnName];
2515

26-
describe('mozRequestAnimationFrame', function () {
27-
it('should run the passed callback in a zone', function (done) {
28-
testZone.run(function() {
29-
if (typeof mozRequestAnimationFrame === 'undefined') {
30-
console.log('WARNING: skipping mozRequestAnimationFrame test (missing this API)');
31-
return done();
32-
}
33-
34-
// Some browsers (especially Safari) do not fire mozRequestAnimationFrame
35-
// if they are offscreen. We can disable this test for those browsers and
36-
// assume the patch works if setTimeout works, since they are mechanically
37-
// the same
38-
mozRequestAnimationFrame(function () {
39-
expect(zone).toBeDirectChildOf(testZone);
16+
it('should be tolerant of invalid arguments', function (done) {
17+
testZone.run(function () {
18+
// rAF throws an error on invalid arguments, so expect that.
19+
expect(function () {
20+
rAF(null);
21+
}).toThrow();
4022
done();
4123
});
4224
});
43-
});
44-
});
4525

46-
describe('webkitRequestAnimationFrame', function () {
47-
it('should run the passed callback in a zone', function (done) {
48-
testZone.run(function() {
49-
if (typeof webkitRequestAnimationFrame === 'undefined') {
50-
console.log('WARNING: skipping webkitRequestAnimationFrame test (missing this API)');
51-
return done();
52-
}
26+
it('should bind to same zone when called recursively', function (done) {
27+
testZone.run(function () {
28+
var frames = 0;
5329

54-
// Some browsers (especially Safari) do not fire webkitRequestAnimationFrame
55-
// if they are offscreen. We can disable this test for those browsers and
56-
// assume the patch works if setTimeout works, since they are mechanically
57-
// the same
58-
webkitRequestAnimationFrame(function () {
59-
expect(zone).toBeDirectChildOf(testZone);
60-
done();
30+
function frameCallback() {
31+
expect(zone === testZone).toBe(true);
32+
if (frames++ > 15) {
33+
return done();
34+
}
35+
rAF(frameCallback);
36+
}
37+
38+
rAF(frameCallback);
6139
});
6240
});
63-
});
41+
}));
6442
});
6543
});

0 commit comments

Comments
 (0)