/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/zone.js/lib/common/timers.ts
185 строк
6 KB
lazerg
fix(zone.js): guard against null handle when detecting refreshable timers
03 авг 2026, 19:14
03 авг 2026, 19:14
36c2197
Код
Авторство
О чём код?
/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ /** * @fileoverview * @suppress {missingRequire} */ import { isFunction, isNumber, patchMethod, scheduleMacroTaskWithCurrentZone, zoneSymbol, } from './utils'; export const taskSymbol = zoneSymbol('zoneTask'); interface TimerOptions extends TaskData { handleId?: number; args: any[]; } export function patchTimer(window: any, setName: string, cancelName: string, nameSuffix: string) { let setNative: Function | null = null; let clearNative: Function | null = null; setName += nameSuffix; cancelName += nameSuffix; const tasksByHandleId: {[id: number]: Task} = {}; function scheduleTask(task: Task) { const data = <TimerOptions>task.data; data.args[0] = function () { return task.invoke.apply(this, arguments); }; const handleOrId = setNative!.apply(window, data.args); // Whlist on Node.js when get can the ID by using `[Symbol.toPrimitive]()` we do // to this so that we do not cause potentally leaks when using `setTimeout` // since this can be periodic when using `.refresh`. if (isNumber(handleOrId)) { data.handleId = handleOrId; } else { data.handle = handleOrId; // On Node.js a timeout and interval can be restarted over and over again by using the `.refresh` method. data.isRefreshable = isFunction(handleOrId?.refresh); } return task; } function clearTask(task: Task) { const {handle, handleId} = task.data!; return clearNative!.call(window, handle ?? handleId); } setNative = patchMethod( window, setName, (delegate: Function) => function (self: any, args: any[]) { if (isFunction(args[0])) { const options: TimerOptions = { isRefreshable: false, isPeriodic: nameSuffix === 'Interval', delay: nameSuffix === 'Timeout' || nameSuffix === 'Interval' ? args[1] || 0 : undefined, args: args, }; const callback = args[0]; args[0] = function timer(this: unknown) { try { return callback.apply(this, arguments); } finally { // issue-934, task will be cancelled // even it is a periodic task such as // setInterval // https://github.com/angular/angular/issues/40387 // Cleanup tasksByHandleId should be handled before scheduleTask // Since some zoneSpec may intercept and doesn't trigger // scheduleFn(scheduleTask) provided here. const {handle, handleId, isPeriodic, isRefreshable} = options; if (!isPeriodic && !isRefreshable) { if (handleId) { // in non-nodejs env, we remove timerId // from local cache delete tasksByHandleId[handleId]; } else if (handle) { // Node returns complex objects as handleIds // we remove task reference from timer object handle[taskSymbol] = null; } } } }; const task = scheduleMacroTaskWithCurrentZone( setName, args[0], options, scheduleTask, clearTask, ); if (!task) { return task; } // Node.js must additionally support the ref and unref functions. const {handleId, handle, isRefreshable, isPeriodic} = <TimerOptions>task.data; if (handleId) { // for non nodejs env, we save handleId: task // mapping in local cache for clearTimeout tasksByHandleId[handleId] = task; } else if (handle) { // for nodejs env, we save task // reference in timerId Object for clearTimeout handle[taskSymbol] = task; if (isRefreshable && !isPeriodic) { const originalRefresh = handle.refresh; handle.refresh = function () { const {zone, state} = task as any; if (state === 'notScheduled') { (task as any)._state = 'scheduled'; zone._updateTaskCount(task, 1); } else if (state === 'running') { (task as any)._state = 'scheduling'; } return originalRefresh.call(this); }; } } return handle ?? handleId ?? task; } else { // cause an error by calling it directly. return delegate.apply(window, args); } }, ); clearNative = patchMethod( window, cancelName, (delegate: Function) => function (self: any, args: any[]) { const id = args[0]; let task: Task; if (isNumber(id)) { // non nodejs env. task = tasksByHandleId[id]; delete tasksByHandleId[id]; } else { // nodejs env ?? other environments. task = id?.[taskSymbol]; if (task) { id[taskSymbol] = null; } else { task = id; } } if (task?.type) { if (task.cancelFn) { // Do not cancel already canceled functions task.zone.cancelTask(task); } } else { // cause an error by calling it directly. delegate.apply(window, args); } }, ); }