From 9104064e8c4f95fd20d94b48d535b63974742920 Mon Sep 17 00:00:00 2001 From: liuguiyuan <875079152@qq.com> Date: Wed, 8 Apr 2026 18:53:42 +0800 Subject: [PATCH 1/2] fix(mask): stop requestAnimationFrame loop on dispose to prevent memory leak The #moveCursorToTarget() method recursively schedules itself via requestAnimationFrame, creating a continuous animation loop for the AI cursor. However, dispose() only removes the DOM wrapper element without stopping this loop, causing: - CPU waste: rAF callback continues executing every frame (~60fps) after the mask is disposed, performing unnecessary calculations on a detached cursor element. - Resource leak: Each SimulatorMask instance creates an unrecoverable animation loop that persists for the lifetime of the page. - Console noise: style assignments to removed DOM nodes may produce browser warnings. Fix: Add a #disposed boolean flag, checked at the top of #moveCursorToTarget() to short-circuit the recursion. Set the flag to true in dispose() before removing DOM elements. Changes: - Add #disposed field (default false) - Guard #moveCursorToTarget() with early return when #disposed - Set #disposed = true in dispose() before cleanup --- packages/page-controller/src/mask/SimulatorMask.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/page-controller/src/mask/SimulatorMask.ts b/packages/page-controller/src/mask/SimulatorMask.ts index 41cbdb4..ec986cc 100644 --- a/packages/page-controller/src/mask/SimulatorMask.ts +++ b/packages/page-controller/src/mask/SimulatorMask.ts @@ -10,6 +10,8 @@ export class SimulatorMask extends EventTarget { wrapper = document.createElement('div') motion: Motion | null = null + #disposed = false + #cursor = document.createElement('div') #currentCursorX = 0 @@ -129,6 +131,7 @@ export class SimulatorMask extends EventTarget { } #moveCursorToTarget() { + if (this.#disposed) return const newX = this.#currentCursorX + (this.#targetCursorX - this.#currentCursorX) * 0.2 const newY = this.#currentCursorY + (this.#targetCursorY - this.#currentCursorY) * 0.2 @@ -200,6 +203,7 @@ export class SimulatorMask extends EventTarget { } dispose() { + this.#disposed = true console.log('dispose SimulatorMask') this.motion?.dispose() this.wrapper.remove() From da67f3b07effe12219f10a1a757e4b500034bdf7 Mon Sep 17 00:00:00 2001 From: Simon <10131203+gaomeng1900@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:13:41 +0800 Subject: [PATCH 2/2] chore(controller): .disposed guard --- packages/page-controller/src/mask/SimulatorMask.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/page-controller/src/mask/SimulatorMask.ts b/packages/page-controller/src/mask/SimulatorMask.ts index ec986cc..1e6984e 100644 --- a/packages/page-controller/src/mask/SimulatorMask.ts +++ b/packages/page-controller/src/mask/SimulatorMask.ts @@ -132,6 +132,7 @@ export class SimulatorMask extends EventTarget { #moveCursorToTarget() { if (this.#disposed) return + const newX = this.#currentCursorX + (this.#targetCursorX - this.#currentCursorX) * 0.2 const newY = this.#currentCursorY + (this.#targetCursorY - this.#currentCursorY) * 0.2 @@ -159,11 +160,15 @@ export class SimulatorMask extends EventTarget { } setCursorPosition(x: number, y: number) { + if (this.#disposed) return + this.#targetCursorX = x this.#targetCursorY = y } triggerClickAnimation() { + if (this.#disposed) return + this.#cursor.classList.remove(cursorStyles.clicking) // Force reflow to restart animation void this.#cursor.offsetHeight @@ -171,7 +176,7 @@ export class SimulatorMask extends EventTarget { } show() { - if (this.shown) return + if (this.shown || this.#disposed) return this.shown = true this.motion?.start() @@ -189,7 +194,7 @@ export class SimulatorMask extends EventTarget { } hide() { - if (!this.shown) return + if (!this.shown || this.#disposed) return this.shown = false this.motion?.fadeOut()