From 68f2e655bfbfecd78e2b072d0c5c1fe60722ee0e Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Thu, 11 Jun 2026 10:19:15 -0500 Subject: [PATCH 1/2] fix: Browser handler leak --- CHANGELOG.md | 1 + src/engine/util/browser.ts | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 192e87830c..84014d4d77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Fixed issue where `BrowserComponent.off()` could never remove event listeners because it passed the raw handler to `removeEventListener` instead of the decorated wrapper registered with `addEventListener`. This caused permanent listener leaks on resize handlers in Screen and Loader. - Fixed issue where the first action in a sequence would not execute after calling `clearActions()` mid-execution. All action types now properly reset their initialization state when stopped, resolving issue #3468 - Performance: Font/Text now use smaller texture sizes, improving performance on Safari especially when rendering text diff --git a/src/engine/util/browser.ts b/src/engine/util/browser.ts index db58e697c1..c9766e05d1 100644 --- a/src/engine/util/browser.ts +++ b/src/engine/util/browser.ts @@ -5,21 +5,24 @@ export interface NativeEventable { export class BrowserComponent { private _paused = false; - private _nativeHandlers: { [key: string]: (handler: any) => void } = {}; + private _handlers = new Map void; wrapper: (evt: any) => void }>(); on(eventName: string, handler: (evt: any) => void): void { - if (this._nativeHandlers[eventName]) { - this.off(eventName, this._nativeHandlers[eventName]); + const existing = this._handlers.get(eventName); + if (existing) { + this.nativeComponent.removeEventListener(eventName, existing.wrapper); } - this._nativeHandlers[eventName] = this._decorate(handler); - this.nativeComponent.addEventListener(eventName, this._nativeHandlers[eventName]); + const wrapper = this._decorate(handler); + this._handlers.set(eventName, { handler, wrapper }); + this.nativeComponent.addEventListener(eventName, wrapper); } off(eventName: string, handler?: (event: any) => void): void { - if (!handler) { - handler = this._nativeHandlers[eventName]; + const entry = this._handlers.get(eventName); + if (!entry) return; + if (!handler || entry.handler === handler) { + this.nativeComponent.removeEventListener(eventName, entry.wrapper); + this._handlers.delete(eventName); } - this.nativeComponent.removeEventListener(eventName, handler); - this._nativeHandlers[eventName] = null; } private _decorate(handler: (evt: any) => void): (evt: any) => void { @@ -39,8 +42,8 @@ export class BrowserComponent { } public clear() { - for (const event in this._nativeHandlers) { - this.off(event); + for (const eventName of this._handlers.keys()) { + this.off(eventName); } } From e60d9e089e19d95f926bc674120d8d886d650e50 Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Sun, 14 Jun 2026 18:42:54 -0500 Subject: [PATCH 2/2] fix lint --- src/engine/util/browser.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engine/util/browser.ts b/src/engine/util/browser.ts index c9766e05d1..3083dfb5be 100644 --- a/src/engine/util/browser.ts +++ b/src/engine/util/browser.ts @@ -18,7 +18,9 @@ export class BrowserComponent { } off(eventName: string, handler?: (event: any) => void): void { const entry = this._handlers.get(eventName); - if (!entry) return; + if (!entry) { + return; + } if (!handler || entry.handler === handler) { this.nativeComponent.removeEventListener(eventName, entry.wrapper); this._handlers.delete(eventName);