mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 22:59:14 +00:00
## Summary Implemented cross-browser requestIdleCallback polyfill to fix Safari crashes during graph initialization. ## Changes - **What**: Added [requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback) polyfill following [VS Code's pattern](https://github.com/microsoft/vscode/blob/main/src/vs/base/common/async.ts) with setTimeout fallback for Safari - **Breaking**: None - maintains existing GraphView behavior ## Review Focus Safari compatibility testing and timeout handling in the 15ms fallback window. Verify that initialization tasks (keybindings, server config, model loading) still execute properly on Safari iOS. ## References - [VS Code async.ts implementation](https://github.com/microsoft/vscode/blob/main/src/vs/base/common/async.ts) - Source pattern for our polyfill - [MDN requestIdleCallback](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback) - Browser API documentation - [Safari requestIdleCallback support](https://caniuse.com/requestidlecallback) - Browser compatibility table Fixes CLOUD-FRONTEND-STAGING-N ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5664-fix-add-Safari-requestIdleCallback-polyfill-2736d73d365081cdbcf1fb816fe098d6) by [Unito](https://www.unito.io) Co-authored-by: Claude <noreply@anthropic.com>
99 lines
2.5 KiB
TypeScript
99 lines
2.5 KiB
TypeScript
/**
|
|
* Cross-browser async utilities for scheduling tasks during browser idle time
|
|
* with proper fallbacks for browsers that don't support requestIdleCallback.
|
|
*
|
|
* Implementation based on:
|
|
* https://github.com/microsoft/vscode/blob/main/src/vs/base/common/async.ts
|
|
*/
|
|
|
|
interface IdleDeadline {
|
|
didTimeout: boolean
|
|
timeRemaining(): number
|
|
}
|
|
|
|
interface IDisposable {
|
|
dispose(): void
|
|
}
|
|
|
|
/**
|
|
* Internal implementation function that handles the actual scheduling logic.
|
|
* Uses feature detection to determine whether to use native requestIdleCallback
|
|
* or fall back to setTimeout-based implementation.
|
|
*/
|
|
let _runWhenIdle: (
|
|
targetWindow: any,
|
|
callback: (idle: IdleDeadline) => void,
|
|
timeout?: number
|
|
) => IDisposable
|
|
|
|
/**
|
|
* Execute the callback during the next browser idle period.
|
|
* Falls back to setTimeout-based scheduling in browsers without native support.
|
|
*/
|
|
export let runWhenGlobalIdle: (
|
|
callback: (idle: IdleDeadline) => void,
|
|
timeout?: number
|
|
) => IDisposable
|
|
|
|
// Self-invoking function to set up the idle callback implementation
|
|
;(function () {
|
|
const safeGlobal: any = globalThis
|
|
|
|
if (
|
|
typeof safeGlobal.requestIdleCallback !== 'function' ||
|
|
typeof safeGlobal.cancelIdleCallback !== 'function'
|
|
) {
|
|
// Fallback implementation for browsers without native support (e.g., Safari)
|
|
_runWhenIdle = (_targetWindow, runner, _timeout?) => {
|
|
setTimeout(() => {
|
|
if (disposed) {
|
|
return
|
|
}
|
|
|
|
// Simulate IdleDeadline - give 15ms window (one frame at ~64fps)
|
|
const end = Date.now() + 15
|
|
const deadline: IdleDeadline = {
|
|
didTimeout: true,
|
|
timeRemaining() {
|
|
return Math.max(0, end - Date.now())
|
|
}
|
|
}
|
|
|
|
runner(Object.freeze(deadline))
|
|
})
|
|
|
|
let disposed = false
|
|
return {
|
|
dispose() {
|
|
if (disposed) {
|
|
return
|
|
}
|
|
disposed = true
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Native requestIdleCallback implementation
|
|
_runWhenIdle = (targetWindow: typeof safeGlobal, runner, timeout?) => {
|
|
const handle: number = targetWindow.requestIdleCallback(
|
|
runner,
|
|
typeof timeout === 'number' ? { timeout } : undefined
|
|
)
|
|
|
|
let disposed = false
|
|
return {
|
|
dispose() {
|
|
if (disposed) {
|
|
return
|
|
}
|
|
disposed = true
|
|
targetWindow.cancelIdleCallback(handle)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
runWhenGlobalIdle = (runner, timeout) =>
|
|
_runWhenIdle(globalThis, runner, timeout)
|
|
})()
|