1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
// SPDX-FileCopyrightText: 2021 Evan Welsh <contact@evanwelsh.com>
/* exported setTimeout, setInterval, clearTimeout, clearInterval */
/* eslint no-implicit-coercion: ["error", {"allow": ["+"]}] */
// Note: implicit coercion with + is used to perform the ToNumber algorithm from
// the timers specification
/**
* @param {number} delay a number value (in milliseconds)
*/
function validateDelay(delay) {
// |0 always returns a signed 32-bit integer.
return Math.max(0, +delay | 0);
}
/** @type {Map<GLib.Source, number>} */
const timeouts = new Map();
/**
* @param {GLib.Source} source the source to add to our map
*/
function addSource(source) {
const id = source.attach(null);
timeouts.set(source, id);
}
/**
* @param {GLib.Source} source the source object to remove from our map
*/
function releaseSource(source) {
timeouts.delete(source);
}
/**
* @param {unknown} thisArg 'this' argument
* @returns {asserts thisArg is (null | undefined | typeof globalThis)}
*/
function checkThis(thisArg) {
if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis)
throw new TypeError('Illegal invocation');
}
/**
* @param {number} timeout a timeout in milliseconds
* @param {(...args) => any} handler a callback
* @returns {GLib.Source}
*/
function createTimeoutSource(timeout, handler) {
const source = imports.gi.GLib.timeout_source_new(timeout);
source.set_priority(imports.gi.GLib.PRIORITY_DEFAULT);
imports.gi.GObject.source_set_closure(source, handler);
return source;
}
/**
* @this {typeof globalThis}
* @param {(...args) => any} callback a callback function
* @param {number} delay the duration in milliseconds to wait before running callback
* @param {...any} args arguments to pass to callback
*/
function setTimeout(callback, delay = 0, ...args) {
checkThis(this);
delay = validateDelay(delay);
const boundCallback = callback.bind(globalThis, ...args);
const source = createTimeoutSource(delay, () => {
if (!timeouts.has(source))
return imports.gi.GLib.SOURCE_REMOVE;
boundCallback();
releaseSource(source);
import.meta.importSync('_promiseNative').drainMicrotaskQueue();
return imports.gi.GLib.SOURCE_REMOVE;
});
addSource(source);
return source;
}
/**
* @this {typeof globalThis}
* @param {(...args) => any} callback a callback function
* @param {number} delay the duration in milliseconds to wait between calling callback
* @param {...any} args arguments to pass to callback
*/
function setInterval(callback, delay = 0, ...args) {
checkThis(this);
delay = validateDelay(delay);
const boundCallback = callback.bind(globalThis, ...args);
const source = createTimeoutSource(delay, () => {
if (!timeouts.has(source))
return imports.gi.GLib.SOURCE_REMOVE;
boundCallback();
import.meta.importSync('_promiseNative').drainMicrotaskQueue();
return imports.gi.GLib.SOURCE_CONTINUE;
});
addSource(source);
return source;
}
/**
* @param {GLib.Source} source the timeout to clear
*/
function _clearTimer(source) {
if (!timeouts.has(source))
return;
if (source) {
source.destroy();
releaseSource(source);
}
}
/**
* @param {GLib.Source} timeout the timeout to clear
*/
function clearTimeout(timeout = null) {
_clearTimer(timeout);
}
/**
* @param {Glib.Source} timeout the timeout to clear
*/
function clearInterval(timeout = null) {
_clearTimer(timeout);
}
Object.defineProperty(globalThis, 'setTimeout', {
configurable: false,
enumerable: true,
writable: true,
value: setTimeout,
});
Object.defineProperty(globalThis, 'setInterval', {
configurable: false,
enumerable: true,
writable: true,
value: setInterval,
});
Object.defineProperty(globalThis, 'clearTimeout', {
configurable: false,
enumerable: true,
writable: true,
value: clearTimeout,
});
Object.defineProperty(globalThis, 'clearInterval', {
configurable: false,
enumerable: true,
writable: true,
value: clearInterval,
});
|