Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "fix: fix memory leaks\n\n",
"type": "none",
"packageName": "@visactor/vrender-animate"
}
],
"packageName": "@visactor/vrender-animate",
"email": "lixuef1313@163.com"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "fix: fix memory leaks\n\n",
"type": "none",
"packageName": "@visactor/vrender-components"
}
],
"packageName": "@visactor/vrender-components",
"email": "lixuef1313@163.com"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "fix: fix memory leaks\n\n",
"type": "none",
"packageName": "@visactor/vrender-core"
}
],
"packageName": "@visactor/vrender-core",
"email": "lixuef1313@163.com"
}
2 changes: 1 addition & 1 deletion packages/vrender-components/src/data-zoom/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export class DataZoomInteraction extends EventEmitter {
}

clearVGlobalEvents() {
(vglobal.env === 'browser' ? vglobal : this.stage).addEventListener('touchmove', this._handleTouchMove, {
(vglobal.env === 'browser' ? vglobal : this.stage).removeEventListener('touchmove', this._handleTouchMove, {
passive: false
});
}
Expand Down
7 changes: 7 additions & 0 deletions packages/vrender-components/src/player/base-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,4 +390,11 @@ export abstract class BasePlayer<T> extends AbstractComponent<Required<PlayerAtt
value: this._data[dataIndex]
});
}

release(all: boolean) {
if (!this._sliderVisible) {
this._slider.release(all);
}
super.release(all);
}
}
2 changes: 1 addition & 1 deletion packages/vrender-components/src/slider/slider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,7 @@ export class Slider extends AbstractComponent<Required<SliderAttributes>> {
* 浏览器上的事件必须解绑,防止内存泄漏,场景树上的事件会自动解绑
*/
super.release(all);
(vglobal.env === 'browser' ? vglobal : this.stage).addEventListener('touchmove', this._handleTouchMove, {
(vglobal.env === 'browser' ? vglobal : this.stage).removeEventListener('touchmove', this._handleTouchMove, {
passive: false
});
this._clearAllDragEvents();
Expand Down
111 changes: 92 additions & 19 deletions packages/vrender-core/src/common/event-listener-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ import type { IEventListenerManager } from '../interface/event-listener-manager'
export class EventListenerManager implements IEventListenerManager {
/**
* Map that stores the mapping from original listeners to wrapped listeners
* Structure: Map<eventType, Map<originalListener, wrappedListener>>
* Structure: Map<eventType, Map<originalListener, Map<capture, wrappedRecord>>>
*/
protected _listenerMap: Map<string, Map<EventListenerOrEventListenerObject, EventListener>>;
protected _listenerMap: Map<
string,
Map<
EventListenerOrEventListenerObject,
Map<boolean, { wrappedListener: EventListener; options?: boolean | AddEventListenerOptions }>
>
>;

/**
* Transformer function that transforms the event
Expand Down Expand Up @@ -44,6 +50,16 @@ export class EventListenerManager implements IEventListenerManager {
return;
}

const capture = this._resolveCapture(options);
const once = this._resolveOnce(options);
const listenerTypeMap = this._getOrCreateListenerTypeMap(type);
const wrappedMap = this._getOrCreateWrappedMap(listenerTypeMap, listener);

// Align with native behavior: adding same (type, listener, capture) repeatedly is a no-op.
if (wrappedMap.has(capture)) {
return;
}

// Create a wrapped listener that applies the transformation
const wrappedListener = (event: Event) => {
const transformedEvent = this._eventListenerTransformer(event);
Expand All @@ -52,13 +68,15 @@ export class EventListenerManager implements IEventListenerManager {
} else if (listener.handleEvent) {
listener.handleEvent(transformedEvent);
}

// Native once listeners are removed automatically after dispatch, clear the mapping as well.
if (once) {
this._deleteListenerRecord(type, listener, capture);
}
};

// Store the mapping between original and wrapped listener
if (!this._listenerMap.has(type)) {
this._listenerMap.set(type, new Map());
}
this._listenerMap.get(type)!.set(listener, wrappedListener);
wrappedMap.set(capture, { wrappedListener, options });

// Add the wrapped listener
this._nativeAddEventListener(type, wrappedListener, options);
Expand All @@ -79,17 +97,12 @@ export class EventListenerManager implements IEventListenerManager {
return;
}

// Get the wrapped listener from our map
const wrappedListener = this._listenerMap.get(type)?.get(listener);
if (wrappedListener) {
const capture = this._resolveCapture(options);
const wrappedRecord = this._listenerMap.get(type)?.get(listener)?.get(capture);
if (wrappedRecord) {
// Remove the wrapped listener
this._nativeRemoveEventListener(type, wrappedListener, options);

// Remove from our map
this._listenerMap.get(type)!.delete(listener);
if (this._listenerMap.get(type)!.size === 0) {
this._listenerMap.delete(type);
}
this._nativeRemoveEventListener(type, wrappedRecord.wrappedListener, capture);
this._deleteListenerRecord(type, listener, capture);
}
}

Expand All @@ -105,14 +118,74 @@ export class EventListenerManager implements IEventListenerManager {
* Clear all event listeners
*/
clearAllEventListeners(): void {
this._listenerMap.forEach((listenersMap, type) => {
listenersMap.forEach((wrappedListener, originalListener) => {
this._nativeRemoveEventListener(type, wrappedListener, undefined);
this._listenerMap.forEach((listenerMap, type) => {
listenerMap.forEach(wrappedMap => {
wrappedMap.forEach((wrappedRecord, capture) => {
this._nativeRemoveEventListener(type, wrappedRecord.wrappedListener, capture);
});
});
});
this._listenerMap.clear();
}

protected _resolveCapture(options?: boolean | EventListenerOptions | AddEventListenerOptions): boolean {
if (typeof options === 'boolean') {
return options;
}
return !!options?.capture;
}

protected _resolveOnce(options?: boolean | AddEventListenerOptions): boolean {
return typeof options === 'object' && !!options?.once;
}

protected _getOrCreateListenerTypeMap(
type: string
): Map<
EventListenerOrEventListenerObject,
Map<boolean, { wrappedListener: EventListener; options?: boolean | AddEventListenerOptions }>
> {
let listenerTypeMap = this._listenerMap.get(type);
if (!listenerTypeMap) {
listenerTypeMap = new Map();
this._listenerMap.set(type, listenerTypeMap);
}
return listenerTypeMap;
}

protected _getOrCreateWrappedMap(
listenerTypeMap: Map<
EventListenerOrEventListenerObject,
Map<boolean, { wrappedListener: EventListener; options?: boolean | AddEventListenerOptions }>
>,
listener: EventListenerOrEventListenerObject
): Map<boolean, { wrappedListener: EventListener; options?: boolean | AddEventListenerOptions }> {
let wrappedMap = listenerTypeMap.get(listener);
if (!wrappedMap) {
wrappedMap = new Map();
listenerTypeMap.set(listener, wrappedMap);
}
return wrappedMap;
}

protected _deleteListenerRecord(type: string, listener: EventListenerOrEventListenerObject, capture: boolean): void {
const listenerTypeMap = this._listenerMap.get(type);
if (!listenerTypeMap) {
return;
}
const wrappedMap = listenerTypeMap.get(listener);
if (!wrappedMap) {
return;
}
wrappedMap.delete(capture);
if (wrappedMap.size === 0) {
listenerTypeMap.delete(listener);
}
if (listenerTypeMap.size === 0) {
this._listenerMap.delete(type);
}
}

/**
* Native implementation of addEventListener
* To be implemented by derived classes
Expand Down
7 changes: 7 additions & 0 deletions packages/vrender-core/src/event/event-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,13 @@ export class EventManager {
const constructor = event.constructor;

if (!this.eventPool.has(constructor as any)) {
this.eventPool.get(constructor as any).forEach(e => {
e.eventPhase = event.NONE;
e.currentTarget = null;
e.path = [];
e.detailPath = [];
e.target = null;
});
this.eventPool.set(constructor as any, []);
}

Expand Down
1 change: 1 addition & 0 deletions packages/vrender-core/src/graphic/graphic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,7 @@ export abstract class Graphic<T extends Partial<IGraphicAttribute> = Partial<IGr
this.releaseStatus = 'released';
this.stopAnimates();
application.graphicService.onRelease(this);
super.release();
}

protected _emitCustomEvent(type: string, context?: any) {
Expand Down
Loading