mirror of
https://github.com/videojs/video.js.git
synced 2025-03-17 21:18:27 +02:00
perf: Do not add/remove listeners for each timer (#6144)
This commit is contained in:
parent
bd51e9e8db
commit
5ee247762e
@ -98,6 +98,11 @@ class Component {
|
||||
this.childIndex_ = {};
|
||||
this.childNameIndex_ = {};
|
||||
|
||||
this.setTimeoutIds_ = new Set();
|
||||
this.setIntervalIds_ = new Set();
|
||||
this.rafIds_ = new Set();
|
||||
this.clearingTimersOnDispose_ = false;
|
||||
|
||||
// Add any child components in options
|
||||
if (options.initChildren !== false) {
|
||||
this.initChildren();
|
||||
@ -1293,16 +1298,16 @@ class Component {
|
||||
|
||||
fn = Fn.bind(this, fn);
|
||||
|
||||
this.clearTimersOnDispose_();
|
||||
|
||||
timeoutId = window.setTimeout(() => {
|
||||
this.off('dispose', disposeFn);
|
||||
if (this.setTimeoutIds_.has(timeoutId)) {
|
||||
this.setTimeoutIds_.delete(timeoutId);
|
||||
}
|
||||
fn();
|
||||
}, timeout);
|
||||
|
||||
disposeFn = () => this.clearTimeout(timeoutId);
|
||||
|
||||
disposeFn.guid = `vjs-timeout-${timeoutId}`;
|
||||
|
||||
this.on('dispose', disposeFn);
|
||||
this.setTimeoutIds_.add(timeoutId);
|
||||
|
||||
return timeoutId;
|
||||
}
|
||||
@ -1323,13 +1328,10 @@ class Component {
|
||||
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
|
||||
*/
|
||||
clearTimeout(timeoutId) {
|
||||
window.clearTimeout(timeoutId);
|
||||
|
||||
const disposeFn = function() {};
|
||||
|
||||
disposeFn.guid = `vjs-timeout-${timeoutId}`;
|
||||
|
||||
this.off('dispose', disposeFn);
|
||||
if (this.setTimeoutIds_.has(timeoutId)) {
|
||||
this.setTimeoutIds_.delete(timeoutId);
|
||||
window.clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
return timeoutId;
|
||||
}
|
||||
@ -1357,13 +1359,11 @@ class Component {
|
||||
setInterval(fn, interval) {
|
||||
fn = Fn.bind(this, fn);
|
||||
|
||||
this.clearTimersOnDispose_();
|
||||
|
||||
const intervalId = window.setInterval(fn, interval);
|
||||
|
||||
const disposeFn = () => this.clearInterval(intervalId);
|
||||
|
||||
disposeFn.guid = `vjs-interval-${intervalId}`;
|
||||
|
||||
this.on('dispose', disposeFn);
|
||||
this.setIntervalIds_.add(intervalId);
|
||||
|
||||
return intervalId;
|
||||
}
|
||||
@ -1384,13 +1384,10 @@ class Component {
|
||||
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
|
||||
*/
|
||||
clearInterval(intervalId) {
|
||||
window.clearInterval(intervalId);
|
||||
|
||||
const disposeFn = function() {};
|
||||
|
||||
disposeFn.guid = `vjs-interval-${intervalId}`;
|
||||
|
||||
this.off('dispose', disposeFn);
|
||||
if (this.setIntervalIds_.has(intervalId)) {
|
||||
this.setIntervalIds_.delete(intervalId);
|
||||
window.clearInterval(intervalId);
|
||||
}
|
||||
|
||||
return intervalId;
|
||||
}
|
||||
@ -1421,28 +1418,27 @@ class Component {
|
||||
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
|
||||
*/
|
||||
requestAnimationFrame(fn) {
|
||||
// declare as variables so they are properly available in rAF function
|
||||
// eslint-disable-next-line
|
||||
var id, disposeFn;
|
||||
|
||||
if (this.supportsRaf_) {
|
||||
fn = Fn.bind(this, fn);
|
||||
|
||||
id = window.requestAnimationFrame(() => {
|
||||
this.off('dispose', disposeFn);
|
||||
fn();
|
||||
});
|
||||
|
||||
disposeFn = () => this.cancelAnimationFrame(id);
|
||||
|
||||
disposeFn.guid = `vjs-raf-${id}`;
|
||||
this.on('dispose', disposeFn);
|
||||
|
||||
return id;
|
||||
// Fall back to using a timer.
|
||||
if (!this.supportsRaf_) {
|
||||
return this.setTimeout(fn, 1000 / 60);
|
||||
}
|
||||
|
||||
// Fall back to using a timer.
|
||||
return this.setTimeout(fn, 1000 / 60);
|
||||
this.clearTimersOnDispose_();
|
||||
|
||||
// declare as variables so they are properly available in rAF function
|
||||
// eslint-disable-next-line
|
||||
var id;
|
||||
fn = Fn.bind(this, fn);
|
||||
|
||||
id = window.requestAnimationFrame(() => {
|
||||
if (this.rafIds_.has(id)) {
|
||||
this.rafIds_.delete(id);
|
||||
}
|
||||
fn();
|
||||
});
|
||||
this.rafIds_.add(id);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1462,20 +1458,47 @@ class Component {
|
||||
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
|
||||
*/
|
||||
cancelAnimationFrame(id) {
|
||||
if (this.supportsRaf_) {
|
||||
window.cancelAnimationFrame(id);
|
||||
|
||||
const disposeFn = function() {};
|
||||
|
||||
disposeFn.guid = `vjs-raf-${id}`;
|
||||
|
||||
this.off('dispose', disposeFn);
|
||||
|
||||
return id;
|
||||
// Fall back to using a timer.
|
||||
if (!this.supportsRaf_) {
|
||||
return this.clearTimeout(id);
|
||||
}
|
||||
|
||||
// Fall back to using a timer.
|
||||
return this.clearTimeout(id);
|
||||
if (this.rafIds_.has(id)) {
|
||||
this.rafIds_.delete(id);
|
||||
window.cancelAnimationFrame(id);
|
||||
}
|
||||
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to setup `requestAnimationFrame`, `setTimeout`,
|
||||
* and `setInterval`, clearing on dispose.
|
||||
*
|
||||
* > Previously each timer added and removed dispose listeners on it's own.
|
||||
* For better performance it was decided to batch them all, and use `Set`s
|
||||
* to track outstanding timer ids.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
clearTimersOnDispose_() {
|
||||
if (this.clearingTimersOnDispose_) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearingTimersOnDispose_ = true;
|
||||
this.one('dispose', () => {
|
||||
[
|
||||
['rafIds_', 'cancelAnimationFrame'],
|
||||
['setTimeoutIds_', 'clearTimeout'],
|
||||
['setIntervalIds_', 'clearInterval']
|
||||
].forEach(([idName, cancelName]) => {
|
||||
this[idName].forEach(this[cancelName], this);
|
||||
});
|
||||
|
||||
this.clearingTimersOnDispose_ = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -978,25 +978,20 @@ QUnit.test('*AnimationFrame methods fall back to timers if rAF not supported', f
|
||||
|
||||
QUnit.test('setTimeout should remove dispose handler on trigger', function(assert) {
|
||||
const comp = new Component(getFakePlayer());
|
||||
const el = comp.el();
|
||||
const data = DomData.get(el);
|
||||
|
||||
comp.setTimeout(() => {}, 1);
|
||||
|
||||
assert.equal(data.handlers.dispose.length, 2, 'we got a new dispose handler');
|
||||
assert.ok(/vjs-timeout-\d/.test(data.handlers.dispose[1].guid), 'we got a new dispose handler');
|
||||
assert.equal(comp.setTimeoutIds_.size, 1, 'we removed our dispose handle');
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.equal(data.handlers.dispose.length, 1, 'we removed our dispose handle');
|
||||
assert.equal(comp.setTimeoutIds_.size, 0, 'we removed our dispose handle');
|
||||
|
||||
comp.dispose();
|
||||
});
|
||||
|
||||
QUnit.test('requestAnimationFrame should remove dispose handler on trigger', function(assert) {
|
||||
const comp = new Component(getFakePlayer());
|
||||
const el = comp.el();
|
||||
const data = DomData.get(el);
|
||||
const oldRAF = window.requestAnimationFrame;
|
||||
const oldCAF = window.cancelAnimationFrame;
|
||||
|
||||
@ -1012,12 +1007,11 @@ QUnit.test('requestAnimationFrame should remove dispose handler on trigger', fun
|
||||
|
||||
comp.requestAnimationFrame(spyRAF);
|
||||
|
||||
assert.equal(data.handlers.dispose.length, 2, 'we got a new dispose handler');
|
||||
assert.ok(/vjs-raf-\d/.test(data.handlers.dispose[1].guid), 'we got a new dispose handler');
|
||||
assert.equal(comp.rafIds_.size, 1, 'we got a new dispose handler');
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.equal(data.handlers.dispose.length, 1, 'we removed our dispose handle');
|
||||
assert.equal(comp.rafIds_.size, 0, 'we removed our dispose handle');
|
||||
|
||||
comp.dispose();
|
||||
|
||||
@ -1025,6 +1019,116 @@ QUnit.test('requestAnimationFrame should remove dispose handler on trigger', fun
|
||||
window.cancelAnimationFrame = oldCAF;
|
||||
});
|
||||
|
||||
QUnit.test('requestAnimationFrame should remove dispose handler on trigger', 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.requestAnimationFrame(spyRAF);
|
||||
|
||||
assert.equal(comp.rafIds_.size, 1, 'we got a new dispose handler');
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.equal(comp.rafIds_.size, 0, 'we removed our dispose handle');
|
||||
|
||||
comp.dispose();
|
||||
|
||||
window.requestAnimationFrame = oldRAF;
|
||||
window.cancelAnimationFrame = oldCAF;
|
||||
});
|
||||
|
||||
QUnit.test('setTimeout should be canceled on dispose', function(assert) {
|
||||
const comp = new Component(getFakePlayer());
|
||||
let called = false;
|
||||
let clearId;
|
||||
const setId = comp.setTimeout(() => {
|
||||
called = true;
|
||||
}, 1);
|
||||
|
||||
const clearTimeout = comp.clearTimeout;
|
||||
|
||||
comp.clearTimeout = (id) => {
|
||||
clearId = id;
|
||||
return clearTimeout.call(comp, id);
|
||||
};
|
||||
|
||||
assert.equal(comp.setTimeoutIds_.size, 1, 'we added a timeout id');
|
||||
|
||||
comp.dispose();
|
||||
|
||||
assert.equal(comp.setTimeoutIds_.size, 0, 'we removed our timeout id');
|
||||
assert.equal(clearId, setId, 'clearTimeout was called');
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.equal(called, false, 'setTimeout was never called');
|
||||
});
|
||||
|
||||
QUnit.test('requestAnimationFrame should be canceled on dispose', function(assert) {
|
||||
const comp = new Component(getFakePlayer());
|
||||
let called = false;
|
||||
let clearId;
|
||||
const setId = comp.requestAnimationFrame(() => {
|
||||
called = true;
|
||||
});
|
||||
|
||||
const cancelAnimationFrame = comp.cancelAnimationFrame;
|
||||
|
||||
comp.cancelAnimationFrame = (id) => {
|
||||
clearId = id;
|
||||
return cancelAnimationFrame.call(comp, id);
|
||||
};
|
||||
|
||||
assert.equal(comp.rafIds_.size, 1, 'we added a raf id');
|
||||
|
||||
comp.dispose();
|
||||
|
||||
assert.equal(comp.rafIds_.size, 0, 'we removed a raf id');
|
||||
assert.equal(clearId, setId, 'clearAnimationFrame was called');
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.equal(called, false, 'requestAnimationFrame was never called');
|
||||
});
|
||||
|
||||
QUnit.test('setInterval should be canceled on dispose', function(assert) {
|
||||
const comp = new Component(getFakePlayer());
|
||||
let called = false;
|
||||
let clearId;
|
||||
const setId = comp.setInterval(() => {
|
||||
called = true;
|
||||
});
|
||||
|
||||
const clearInterval = comp.clearInterval;
|
||||
|
||||
comp.clearInterval = (id) => {
|
||||
clearId = id;
|
||||
return clearInterval.call(comp, id);
|
||||
};
|
||||
|
||||
assert.equal(comp.setIntervalIds_.size, 1, 'we added an interval id');
|
||||
|
||||
comp.dispose();
|
||||
|
||||
assert.equal(comp.setIntervalIds_.size, 0, 'we removed a raf id');
|
||||
assert.equal(clearId, setId, 'clearInterval was called');
|
||||
|
||||
this.clock.tick(1);
|
||||
|
||||
assert.equal(called, false, 'setInterval was never called');
|
||||
});
|
||||
|
||||
QUnit.test('$ and $$ functions', function(assert) {
|
||||
const comp = new Component(getFakePlayer());
|
||||
const contentEl = document.createElement('div');
|
||||
|
Loading…
x
Reference in New Issue
Block a user