100 lines
3.5 KiB
JavaScript
100 lines
3.5 KiB
JavaScript
(function () {
|
|
const App = owl.App;
|
|
|
|
const stopPromises = [];
|
|
const schedulers = new Set();
|
|
|
|
function isRunning(scheduler) {
|
|
for (const fiber of scheduler.tasks) {
|
|
if (fiber.node.status !== 2) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function hookIntoScheduler(scheduler) {
|
|
const { flush: originalFlush } = scheduler;
|
|
scheduler.flush = function flush() {
|
|
originalFlush.call(this, ...arguments);
|
|
window.requestAnimationFrame(() => {
|
|
if ([...schedulers].every((scheduler) => !isRunning(scheduler))) {
|
|
while (stopPromises.length) {
|
|
stopPromises.pop().resolve();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
owl.App = class extends App {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.setup();
|
|
hookIntoScheduler(this.scheduler);
|
|
schedulers.add(this.scheduler);
|
|
}
|
|
destroy() {
|
|
schedulers.delete(this.scheduler);
|
|
super.destroy();
|
|
}
|
|
setup() {}
|
|
};
|
|
|
|
/**
|
|
* Returns a promise resolved the next time OWL stops rendering.
|
|
*
|
|
* @param {function} func function which, when called, is
|
|
* expected to trigger OWL render(s).
|
|
* @param {number} [timeoutDelay=5000] in ms
|
|
* @returns {Promise}
|
|
*/
|
|
owl.App.afterNextRender = async function afterNextRender(func, timeoutDelay = 5000) {
|
|
// Define the potential errors outside of the promise to get a proper
|
|
// trace if they happen.
|
|
const startError = new Error("Timeout: the render didn't start.");
|
|
const stopError = new Error("Timeout: the render didn't stop.");
|
|
// Set up the timeout to reject if no render happens.
|
|
let timeoutNoRender;
|
|
|
|
const timeoutProm = new Promise((resolve, reject) => {
|
|
timeoutNoRender = setTimeout(() => {
|
|
let error = startError;
|
|
const runningSchedulers = [...schedulers].filter(isRunning);
|
|
if (runningSchedulers.length) {
|
|
error = stopError;
|
|
}
|
|
console.warn(
|
|
error,
|
|
runningSchedulers.map((s) => [...s.tasks].map((f) => [f, f.node.status])),
|
|
runningSchedulers
|
|
);
|
|
reject(error);
|
|
}, timeoutDelay);
|
|
});
|
|
// Set up the promise to resolve if a render happens.
|
|
let resolve;
|
|
const prom = new Promise((res) => {
|
|
resolve = res;
|
|
});
|
|
prom.resolve = resolve;
|
|
stopPromises.push(prom);
|
|
// Start the function expected to trigger a render after the promise
|
|
// has been registered to not miss any potential render.
|
|
const funcRes = func();
|
|
// Make them race (first to resolve/reject wins).
|
|
await Promise.race([prom, timeoutProm]);
|
|
clearTimeout(timeoutNoRender);
|
|
// Wait the end of the function to ensure all potential effects are
|
|
// taken into account during the following verification step.
|
|
await funcRes;
|
|
// Wait one more frame to make sure no new render has been queued.
|
|
await new Promise(function (resolve) {
|
|
setTimeout(() => window.requestAnimationFrame(() => resolve()));
|
|
});
|
|
if ([...schedulers].some(isRunning)) {
|
|
await afterNextRender(() => {}, timeoutDelay);
|
|
}
|
|
};
|
|
})();
|