From 14c5f1602cdd337df14c37ea8df777578219f744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20S=C3=A1nchez=20Ortega?= Date: Thu, 2 Feb 2017 10:57:57 +0100 Subject: [PATCH] Scrubbing of detached DOM elements, prevents memory leaks (#5265) * Scrubbing of detached DOM elements, prevents memory leaks and fixes #5263 * Make linter happy --- src/control/Control.js | 5 +++++ src/dom/DomEvent.Pointer.js | 1 - src/dom/DomEvent.js | 17 ++++++++++++----- src/layer/vector/Canvas.js | 7 +++++++ src/layer/vector/Renderer.js | 2 +- src/layer/vector/SVG.js | 7 +++++++ src/map/Map.js | 26 +++++++++++++++++++++----- src/map/handler/Map.BoxZoom.js | 6 ++++++ 8 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/control/Control.js b/src/control/Control.js index bf57d1d8..6db9f4bb 100644 --- a/src/control/Control.js +++ b/src/control/Control.js @@ -161,6 +161,11 @@ Map.include({ }, _clearControlPos: function () { + for (var i in this._controlCorners) { + DomUtil.remove(this._controlCorners[i]); + } DomUtil.remove(this._controlContainer); + delete this._controlCorners; + delete this._controlContainer; } }); diff --git a/src/dom/DomEvent.Pointer.js b/src/dom/DomEvent.Pointer.js index 3a130ebd..5ec133b8 100644 --- a/src/dom/DomEvent.Pointer.js +++ b/src/dom/DomEvent.Pointer.js @@ -23,7 +23,6 @@ export var _pointersCount = 0; // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 export function addPointerListener(obj, type, handler, id) { - if (type === 'touchstart') { _addPointerStart(obj, handler, id); diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js index 22a02047..9f86e59c 100644 --- a/src/dom/DomEvent.js +++ b/src/dom/DomEvent.js @@ -37,6 +37,8 @@ export function on(obj, types, fn, context) { return this; } +var eventsKey = '_leaflet_events'; + // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this // Removes a previously added listener function. If no function is specified, // it will remove all the listeners of that particular DOM event from the element. @@ -46,25 +48,30 @@ export function on(obj, types, fn, context) { // @alternative // @function off(el: HTMLElement, eventMap: Object, context?: Object): this // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` + +// @alternative +// @function off(el: HTMLElement): this +// Removes all known event listeners export function off(obj, types, fn, context) { if (typeof types === 'object') { for (var type in types) { removeOne(obj, type, types[type], fn); } - } else { + } else if (types) { types = Util.splitWords(types); for (var i = 0, len = types.length; i < len; i++) { removeOne(obj, types[i], fn, context); } + } else { + for (var j in obj[eventsKey]) { + removeOne(obj, j, obj[eventsKey][j]); + } + delete obj[eventsKey]; } - - return this; } -var eventsKey = '_leaflet_events'; - function addOne(obj, type, fn, context) { var id = type + Util.stamp(fn) + (context ? '_' + Util.stamp(context) : ''); diff --git a/src/layer/vector/Canvas.js b/src/layer/vector/Canvas.js index 5e3e1d94..c1171ae0 100644 --- a/src/layer/vector/Canvas.js +++ b/src/layer/vector/Canvas.js @@ -67,6 +67,13 @@ export var Canvas = Renderer.extend({ this._ctx = container.getContext('2d'); }, + _destroyContainer: function () { + delete this._ctx; + L.DomUtil.remove(this._container); + L.DomEvent.off(this._container); + delete this._container; + }, + _updatePaths: function () { if (this._postponeUpdatePaths) { return; } diff --git a/src/layer/vector/Renderer.js b/src/layer/vector/Renderer.js index 64e005cb..d2e5e77d 100644 --- a/src/layer/vector/Renderer.js +++ b/src/layer/vector/Renderer.js @@ -58,8 +58,8 @@ export var Renderer = Layer.extend({ }, onRemove: function () { - DomUtil.remove(this._container); this.off('update', this._updatePaths, this); + this._destroyContainer(); }, getEvents: function () { diff --git a/src/layer/vector/SVG.js b/src/layer/vector/SVG.js index 12a38be0..b5c08548 100644 --- a/src/layer/vector/SVG.js +++ b/src/layer/vector/SVG.js @@ -61,6 +61,13 @@ export var SVG = Renderer.extend({ this._container.appendChild(this._rootGroup); }, + _destroyContainer: function () { + L.DomUtil.remove(this._container); + L.DomEvent.off(this._container); + delete this._container; + delete this._rootGroup; + }, + _onZoomStart: function () { // Drag-then-pinch interactions might mess up the center and zoom. // In this case, the easiest way to prevent this is re-do the renderer diff --git a/src/map/Map.js b/src/map/Map.js index 03365dc3..8b6ba24e 100644 --- a/src/map/Map.js +++ b/src/map/Map.js @@ -716,9 +716,18 @@ export var Map = Evented.extend({ this.fire('unload'); } - for (var i in this._layers) { + var i; + for (i in this._layers) { this._layers[i].remove(); } + for (i in this._panes) { + L.DomUtil.remove(this._panes[i]); + } + + this._layers = []; + this._panes = []; + delete this._mapPane; + delete this._renderer; return this; }, @@ -1507,12 +1516,12 @@ export var Map = Evented.extend({ this.on('zoomanim', function (e) { var prop = DomUtil.TRANSFORM, - transform = proxy.style[prop]; + transform = this._proxy.style[prop]; - DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); + DomUtil.setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); // workaround for case when transform is the same and so transitionend event is not fired - if (transform === proxy.style[prop] && this._animatingZoom) { + if (transform === this._proxy.style[prop] && this._animatingZoom) { this._onZoomTransitionEnd(); } }, this); @@ -1520,8 +1529,15 @@ export var Map = Evented.extend({ this.on('load moveend', function () { var c = this.getCenter(), z = this.getZoom(); - DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1)); + DomUtil.setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); }, this); + + this._on('unload', this._destroyAnimProxy, this); + }, + + _destroyAnimProxy: function () { + L.DomUtil.remove(this._proxy); + delete this._proxy; }, _catchTransitionEnd: function (e) { diff --git a/src/map/handler/Map.BoxZoom.js b/src/map/handler/Map.BoxZoom.js index b24c3b5e..2d2275b9 100644 --- a/src/map/handler/Map.BoxZoom.js +++ b/src/map/handler/Map.BoxZoom.js @@ -25,6 +25,7 @@ export var BoxZoom = Handler.extend({ this._map = map; this._container = map._container; this._pane = map._panes.overlayPane; + map.on('unload', this._destroy, this); }, addHooks: function () { @@ -39,6 +40,11 @@ export var BoxZoom = Handler.extend({ return this._moved; }, + _destroy: function () { + L.DomUtil.remove(this._pane); + delete this._pane; + }, + _resetState: function () { this._moved = false; },