1
0
mirror of https://github.com/videojs/video.js.git synced 2025-07-07 01:07:13 +02:00

feat: Add named requestAnimationFrame to prevent performance issues (#6627)

Make sure we don't create multiple rAFs particularly when in a background tab.

Fixes #5937
This commit is contained in:
Brandon Casey
2020-06-19 14:22:04 -04:00
committed by Gary Katsevman
parent 4d9e1bcccc
commit 6e7cc75aaa
9 changed files with 282 additions and 52 deletions

View File

@ -952,7 +952,7 @@ QUnit.test('should provide interval methods that automatically get cleared on co
assert.ok(intervalsFired === 5, 'Interval was cleared when component was disposed');
});
QUnit.test('should provide *AnimationFrame methods that automatically get cleared on component disposal', function(assert) {
QUnit.test('should provide a requestAnimationFrame method that is cleared on disposal', function(assert) {
const comp = new Component(getFakePlayer());
const oldRAF = window.requestAnimationFrame;
const oldCAF = window.cancelAnimationFrame;
@ -967,7 +967,7 @@ QUnit.test('should provide *AnimationFrame methods that automatically get cleare
const spyRAF = sinon.spy();
comp.requestAnimationFrame(spyRAF);
comp.requestNamedAnimationFrame('testing', spyRAF);
assert.strictEqual(spyRAF.callCount, 0, 'rAF callback was not called immediately');
this.clock.tick(1);
@ -975,11 +975,11 @@ QUnit.test('should provide *AnimationFrame methods that automatically get cleare
this.clock.tick(1);
assert.strictEqual(spyRAF.callCount, 1, 'rAF callback was not called after a second "repaint"');
comp.cancelAnimationFrame(comp.requestAnimationFrame(spyRAF));
comp.cancelNamedAnimationFrame(comp.requestNamedAnimationFrame('testing', spyRAF));
this.clock.tick(1);
assert.strictEqual(spyRAF.callCount, 1, 'second rAF callback was not called because it was cancelled');
comp.requestAnimationFrame(spyRAF);
comp.requestNamedAnimationFrame('testing', spyRAF);
comp.dispose();
this.clock.tick(1);
assert.strictEqual(spyRAF.callCount, 1, 'third rAF callback was not called because the component was disposed');
@ -988,7 +988,43 @@ QUnit.test('should provide *AnimationFrame methods that automatically get cleare
window.cancelAnimationFrame = oldCAF;
});
QUnit.test('*AnimationFrame methods fall back to timers if rAF not supported', function(assert) {
QUnit.test('should provide a requestNamedAnimationFrame method that is cleared on disposal', function(assert) {
const comp = new Component(getFakePlayer());
const oldRAF = window.requestAnimationFrame;
const oldCAF = window.cancelAnimationFrame;
// Stub the window.*AnimationFrame methods with window.setTimeout methods
// so we can control when the callbacks are called via sinon's timer stubs.
window.requestAnimationFrame = (fn) => window.setTimeout(fn, 1);
window.cancelAnimationFrame = (id) => window.clearTimeout(id);
// Make sure the component thinks it supports rAF.
comp.supportsRaf_ = true;
const spyRAF = sinon.spy();
comp.requestNamedAnimationFrame('testing', spyRAF);
assert.strictEqual(spyRAF.callCount, 0, 'rAF callback was not called immediately');
this.clock.tick(1);
assert.strictEqual(spyRAF.callCount, 1, 'rAF callback was called after a "repaint"');
this.clock.tick(1);
assert.strictEqual(spyRAF.callCount, 1, 'rAF callback was not called after a second "repaint"');
comp.cancelNamedAnimationFrame(comp.requestNamedAnimationFrame('testing', spyRAF));
this.clock.tick(1);
assert.strictEqual(spyRAF.callCount, 1, 'second rAF callback was not called because it was cancelled');
comp.requestNamedAnimationFrame('testing', spyRAF);
comp.dispose();
this.clock.tick(1);
assert.strictEqual(spyRAF.callCount, 1, 'third rAF callback was not called because the component was disposed');
window.requestAnimationFrame = oldRAF;
window.cancelAnimationFrame = oldCAF;
});
QUnit.test('requestAnimationFrame falls back to timers if rAF not supported', function(assert) {
const comp = new Component(getFakePlayer());
const oldRAF = window.requestAnimationFrame;
const oldCAF = window.cancelAnimationFrame;
@ -1030,7 +1066,7 @@ QUnit.test('setTimeout should remove dispose handler on trigger', function(asser
comp.dispose();
});
QUnit.test('requestAnimationFrame should remove dispose handler on trigger', function(assert) {
QUnit.test('requestNamedAnimationFrame should remove dispose handler on trigger', function(assert) {
const comp = new Component(getFakePlayer());
const oldRAF = window.requestAnimationFrame;
const oldCAF = window.cancelAnimationFrame;
@ -1045,13 +1081,15 @@ QUnit.test('requestAnimationFrame should remove dispose handler on trigger', fun
const spyRAF = sinon.spy();
comp.requestAnimationFrame(spyRAF);
comp.requestNamedAnimationFrame('testFrame', spyRAF);
assert.equal(comp.rafIds_.size, 1, 'we got a new dispose handler');
assert.equal(comp.rafIds_.size, 1, 'we got a new raf dispose handler');
assert.equal(comp.namedRafs_.size, 1, 'we got a new named raf dispose handler');
this.clock.tick(1);
assert.equal(comp.rafIds_.size, 0, 'we removed our dispose handle');
assert.equal(comp.rafIds_.size, 0, 'we removed our raf dispose handle');
assert.equal(comp.namedRafs_.size, 0, 'we removed our named raf dispose handle');
comp.dispose();
@ -1169,6 +1207,122 @@ QUnit.test('setInterval should be canceled on dispose', function(assert) {
assert.equal(called, false, 'setInterval was never called');
});
QUnit.test('requestNamedAnimationFrame should be canceled on dispose', function(assert) {
const comp = new Component(getFakePlayer());
let called = false;
let clearName;
const setName = comp.requestNamedAnimationFrame('testing', () => {
called = true;
});
const cancelNamedAnimationFrame = comp.cancelNamedAnimationFrame;
comp.cancelNamedAnimationFrame = (name) => {
clearName = name;
return cancelNamedAnimationFrame.call(comp, name);
};
assert.equal(comp.namedRafs_.size, 1, 'we added a named raf');
assert.equal(comp.rafIds_.size, 1, 'we added a raf id');
comp.dispose();
assert.equal(comp.namedRafs_.size, 0, 'we removed a named raf');
assert.equal(comp.rafIds_.size, 0, 'we removed a raf id');
assert.equal(clearName, setName, 'cancelNamedAnimationFrame was called');
this.clock.tick(1);
assert.equal(called, false, 'requestNamedAnimationFrame was never called');
});
QUnit.test('requestNamedAnimationFrame should only allow one raf of a specific name at a time', function(assert) {
const comp = new Component(getFakePlayer());
const calls = {
one: 0,
two: 0,
three: 0
};
const cancelNames = [];
const name = 'testing';
const handlerOne = () => {
assert.equal(comp.namedRafs_.size, 1, 'named raf still exists while function runs');
assert.equal(comp.rafIds_.size, 0, 'raf id does not exist during run');
calls.one++;
};
const handlerTwo = () => {
assert.equal(comp.namedRafs_.size, 1, 'named raf still exists while function runs');
assert.equal(comp.rafIds_.size, 0, 'raf id does not exist during run');
calls.two++;
};
const handlerThree = () => {
assert.equal(comp.namedRafs_.size, 1, 'named raf still exists while function runs');
assert.equal(comp.rafIds_.size, 0, 'raf id does not exist during run');
calls.three++;
};
const oldRAF = window.requestAnimationFrame;
const oldCAF = window.cancelAnimationFrame;
// Make sure the component thinks it supports rAF.
comp.supportsRaf_ = true;
// Stub the window.*AnimationFrame methods with window.setTimeout methods
// so we can control when the callbacks are called via sinon's timer stubs.
window.requestAnimationFrame = (fn) => window.setTimeout(fn, 1);
window.cancelAnimationFrame = (id) => window.clearTimeout(id);
const cancelNamedAnimationFrame = comp.cancelNamedAnimationFrame;
comp.cancelNamedAnimationFrame = (_name) => {
cancelNames.push(_name);
return cancelNamedAnimationFrame.call(comp, _name);
};
comp.requestNamedAnimationFrame(name, handlerOne);
assert.equal(comp.namedRafs_.size, 1, 'we added a named raf');
assert.equal(comp.rafIds_.size, 1, 'we added a raf id');
comp.requestNamedAnimationFrame(name, handlerTwo);
assert.deepEqual(cancelNames, [], 'no named cancels');
assert.equal(comp.namedRafs_.size, 1, 'still only one named raf');
assert.equal(comp.rafIds_.size, 1, 'still only one raf id');
this.clock.tick(1);
assert.equal(comp.namedRafs_.size, 0, 'we removed a named raf');
assert.equal(comp.rafIds_.size, 0, 'we removed a raf id');
assert.deepEqual(calls, {
one: 1,
two: 0,
three: 0
}, 'only handlerOne was called');
comp.requestNamedAnimationFrame(name, handlerOne);
comp.requestNamedAnimationFrame(name, handlerTwo);
comp.requestNamedAnimationFrame(name, handlerThree);
assert.deepEqual(cancelNames, [], 'no named cancels for testing');
assert.equal(comp.namedRafs_.size, 1, 'only added one named raf');
assert.equal(comp.rafIds_.size, 1, 'only added one named raf');
this.clock.tick(1);
assert.equal(comp.namedRafs_.size, 0, 'we removed a named raf');
assert.equal(comp.rafIds_.size, 0, 'we removed a raf id');
assert.deepEqual(calls, {
one: 2,
two: 0,
three: 0
}, 'only the handlerOne called');
window.requestAnimationFrame = oldRAF;
window.cancelAnimationFrame = oldCAF;
});
QUnit.test('$ and $$ functions', function(assert) {
const comp = new Component(getFakePlayer());
const contentEl = document.createElement('div');