export interface Task { id : number ; fn : ( ( didTimeout : boolean ) => void ) | null ; startTime : number ; expirationTime : number ; } type NavigatorScheduling = Navigator & { scheduling : { isInputPending ? : ( ) => boolean ; } ; } ; let taskIdCounter = 1 , isCallbackScheduled = false , isPerformingWork = false , taskQueue = [ ] , currentTask = null , shouldYieldToHost = null , yieldInterval = 5 , deadline = 0 , maxYieldInterval = 300 , scheduleCallback = null , scheduledCallback = null ; const maxSigned31BitInt = 1073741823 ; function setupScheduler ( ) { const channel = new MessageChannel ( ) , port = channel . port2 ; scheduleCallback = ( ) => port . postMessage ( null ) ; channel . port1 . onmessage = ( ) => { if ( scheduledCallback !== null ) { const currentTime = performance . now ( ) ; deadline = currentTime + yieldInterval ; const hasTimeRemaining = true ; try { const hasMoreWork = scheduledCallback ( hasTimeRemaining , currentTime ) ; if ( ! hasMoreWork ) { scheduledCallback = null ; } else port . postMessage ( null ) ; } catch ( error ) { port . postMessage ( null ) ; throw error ; } } } ; if ( navigator && ( navigator as NavigatorScheduling ) . scheduling && ( navigator as NavigatorScheduling ) . scheduling . isInputPending ) { const scheduling = ( navigator as NavigatorScheduling ) . scheduling ; shouldYieldToHost = ( ) => { const currentTime = performance . now ( ) ; if ( currentTime >= deadline ) { if ( scheduling . isInputPending ! ( ) ) { return true ; } return currentTime >= maxYieldInterval ; } else { return false ; } } ; } else { shouldYieldToHost = ( ) => performance . now ( ) >= deadline ; } } function enqueue ( taskQueue , task ) { function findIndex ( ) { let m = 0 ; let n = taskQueue . length - 1 ; while ( m <= n ) { const k = ( n + m ) >> 1 ; const cmp = task . expirationTime - taskQueue [ k ] . expirationTime ; if ( cmp > 0 ) m = k + 1 ; else if ( cmp < 0 ) n = k - 1 ; else return k ; } return m ; } taskQueue . splice ( findIndex ( ) , 0 , task ) ; } export function requestCallback ( fn , options ? ) { if ( ! scheduleCallback ) setupScheduler ( ) ; let startTime = performance . now ( ) , timeout = maxSigned31BitInt ; if ( options && options . timeout ) timeout = options . timeout ; const newTask = { id : taskIdCounter ++ , fn , startTime , expirationTime : startTime + timeout } ; enqueue ( taskQueue , newTask ) ; if ( ! isCallbackScheduled && ! isPerformingWork ) { isCallbackScheduled = true ; scheduledCallback = flushWork ; scheduleCallback ! ( ) ; } return newTask ; } export function cancelCallback ( task ) { task . fn = null ; } function flushWork ( hasTimeRemaining , initialTime ) { isCallbackScheduled = false ; isPerformingWork = true ; try { return workLoop ( hasTimeRemaining , initialTime ) ; } finally { currentTask = null ; isPerformingWork = false ; } } function workLoop ( hasTimeRemaining , initialTime ) { let currentTime = initialTime ; currentTask = taskQueue [ 0 ] || null ; while ( currentTask !== null ) { if ( currentTask . expirationTime > currentTime && ( ! hasTimeRemaining || shouldYieldToHost ! ( ) ) ) { break ; } const callback = currentTask . fn ; if ( callback !== null ) { currentTask . fn = null ; const didUserCallbackTimeout = currentTask . expirationTime <= currentTime ; callback ( didUserCallbackTimeout ) ; currentTime = performance . now ( ) ; if ( currentTask === taskQueue [ 0 ] ) { taskQueue . shift ( ) ; } } else taskQueue . shift ( ) ; currentTask = taskQueue [ 0 ] || null ; } return currentTask !== null ; }