diff --git a/src/ui/control/navigation_control.js b/src/ui/control/navigation_control.js index 63aece262f7..743eb21f1b6 100644 --- a/src/ui/control/navigation_control.js +++ b/src/ui/control/navigation_control.js @@ -173,10 +173,10 @@ class MouseRotateWrapper { move(e: MouseEvent, point: Point) { const map = this.map; - const r = this.mouseRotate.windowMousemove(e, point); + const r = this.mouseRotate.mousemoveWindow(e, point); if (r && r.bearingDelta) map.setBearing(map.getBearing() + r.bearingDelta); if (this.mousePitch) { - const p = this.mousePitch.windowMousemove(e, point); + const p = this.mousePitch.mousemoveWindow(e, point); if (p && p.pitchDelta) map.setPitch(map.getPitch() + p.pitchDelta); } } @@ -208,8 +208,8 @@ class MouseRotateWrapper { } mouseup(e: MouseEvent) { - this.mouseRotate.windowMouseup(e); - if (this.mousePitch) this.mousePitch.windowMouseup(e); + this.mouseRotate.mouseupWindow(e); + if (this.mousePitch) this.mousePitch.mouseupWindow(e); this.offTemp(); } diff --git a/src/ui/handler/box_zoom.js b/src/ui/handler/box_zoom.js index 5665e9a1d6f..be8415ab7e3 100644 --- a/src/ui/handler/box_zoom.js +++ b/src/ui/handler/box_zoom.js @@ -82,7 +82,7 @@ class BoxZoomHandler { this._active = true; } - windowMousemove(e: MouseEvent, point: Point) { + mousemoveWindow(e: MouseEvent, point: Point) { if (!this._active) return; const pos = point; @@ -111,7 +111,7 @@ class BoxZoomHandler { this._box.style.height = `${maxY - minY}px`; } - windowMouseup(e: MouseEvent, point: Point) { + mouseupWindow(e: MouseEvent, point: Point) { if (!this._active) return; if (e.button !== 0) return; diff --git a/src/ui/handler/mouse.js b/src/ui/handler/mouse.js index 8c8d42d2ee1..a2d4f41eeed 100644 --- a/src/ui/handler/mouse.js +++ b/src/ui/handler/mouse.js @@ -45,7 +45,7 @@ class MouseHandler { this._eventButton = eventButton; } - windowMousemove(e: MouseEvent, point: Point) { + mousemoveWindow(e: MouseEvent, point: Point) { const lastPoint = this._lastPoint; if (!lastPoint) return; e.preventDefault(); @@ -58,7 +58,7 @@ class MouseHandler { return this._move(lastPoint, point); } - windowMouseup(e: MouseEvent) { + mouseupWindow(e: MouseEvent) { const eventButton = DOM.mouseButton(e); if (eventButton !== this._eventButton) return; if (this._moved) DOM.suppressClick(); diff --git a/src/ui/handler_manager.js b/src/ui/handler_manager.js index 0a617bd41aa..1589e1d0f59 100644 --- a/src/ui/handler_manager.js +++ b/src/ui/handler_manager.js @@ -18,7 +18,7 @@ import TapDragZoomHandler from './handler/tap_drag_zoom'; import DragPanHandler from './handler/shim/drag_pan'; import DragRotateHandler from './handler/shim/drag_rotate'; import TouchZoomRotateHandler from './handler/shim/touch_zoom_rotate'; -import {extend} from '../util/util'; +import {bindAll, extend} from '../util/util'; import window from '../util/window'; import Point from '@mapbox/point-geometry'; import assert from 'assert'; @@ -105,6 +105,7 @@ class HandlerManager { _changes: Array<[HandlerResult, Object, any]>; _previousActiveHandlers: { [string]: Handler }; _bearingChanged: boolean; + _listeners: Array<[HTMLElement, string, void | {passive?: boolean, capture?: boolean}]>; constructor(map: Map, options: { interactive: boolean, pitchWithRotate: boolean, clickTolerance: number, bearingSnap: number}) { this._map = map; @@ -122,45 +123,56 @@ class HandlerManager { this._addDefaultHandlers(options); - // Bind touchstart and touchmove with passive: false because, even though - // they only fire a map events and therefore could theoretically be - // passive, binding with passive: true causes iOS not to respect - // e.preventDefault() in _other_ handlers, even if they are non-passive - // (see https://bugs.webkit.org/show_bug.cgi?id=184251) - this._addListener(this._el, 'touchstart', {passive: false}); - this._addListener(this._el, 'touchmove', {passive: false}); - this._addListener(this._el, 'touchend'); - this._addListener(this._el, 'touchcancel'); - - this._addListener(this._el, 'mousedown'); - this._addListener(this._el, 'mousemove'); - this._addListener(this._el, 'mouseup'); - - // Bind window-level event listeners for move and up/end events. In the absence of - // the pointer capture API, which is not supported by all necessary platforms, - // window-level event listeners give us the best shot at capturing events that - // fall outside the map canvas element. Use `{capture: true}` for the move event - // to prevent map move events from being fired during a drag. - this._addListener(window.document, 'mousemove', {capture: true}, 'windowMousemove'); - this._addListener(window.document, 'mouseup', undefined, 'windowMouseup'); - - this._addListener(this._el, 'mouseover'); - this._addListener(this._el, 'mouseout'); - this._addListener(this._el, 'dblclick'); - this._addListener(this._el, 'click'); - - this._addListener(this._el, 'keydown', {capture: false}); - this._addListener(this._el, 'keyup'); - - this._addListener(this._el, 'wheel', {passive: false}); - this._addListener(this._el, 'contextmenu'); - - DOM.addEventListener(window, 'blur', () => this.stop()); + bindAll(['handleEvent', 'handleWindowEvent'], this); + + const el = this._el; + + this._listeners = [ + // Bind touchstart and touchmove with passive: false because, even though + // they only fire a map events and therefore could theoretically be + // passive, binding with passive: true causes iOS not to respect + // e.preventDefault() in _other_ handlers, even if they are non-passive + // (see https://bugs.webkit.org/show_bug.cgi?id=184251) + [el, 'touchstart', {passive: false}], + [el, 'touchmove', {passive: false}], + [el, 'touchend', undefined], + [el, 'touchcancel', undefined], + + [el, 'mousedown', undefined], + [el, 'mousemove', undefined], + [el, 'mouseup', undefined], + + // Bind window-level event listeners for move and up/end events. In the absence of + // the pointer capture API, which is not supported by all necessary platforms, + // window-level event listeners give us the best shot at capturing events that + // fall outside the map canvas element. Use `{capture: true}` for the move event + // to prevent map move events from being fired during a drag. + [window.document, 'mousemove', {capture: true}], + [window.document, 'mouseup', undefined], + + [el, 'mouseover', undefined], + [el, 'mouseout', undefined], + [el, 'dblclick', undefined], + [el, 'click', undefined], + + [el, 'keydown', {capture: false}], + [el, 'keyup', undefined], + + [el, 'wheel', {passive: false}], + [el, 'contextmenu', undefined], + + [window, 'blur', undefined] + ]; + + for (const [target, type, listenerOptions] of this._listeners) { + DOM.addEventListener(target, type, target === window.document ? this.handleWindowEvent : this.handleEvent, listenerOptions); + } } - _addListener(element: Element, eventType: string, options: Object, name_?: string) { - const name = name_ || eventType; - DOM.addEventListener(element, eventType, e => this._processInputEvent(e, name), options); + destroy() { + for (const [target, type, listenerOptions] of this._listeners) { + DOM.removeEventListener(target, type, target === window.document ? this.handleWindowEvent : this.handleEvent, listenerOptions); + } } _addDefaultHandlers(options: { interactive: boolean, pitchWithRotate: boolean, clickTolerance: number }) { @@ -257,7 +269,16 @@ class HandlerManager { return false; } - _processInputEvent(e: InputEvent | RenderFrameEvent, eventName?: string) { + handleWindowEvent(e: InputEvent) { + this.handleEvent(e, `${e.type}Window`); + } + + handleEvent(e: InputEvent | RenderFrameEvent, eventName?: string) { + + if (e.type === 'blur') { + this.stop(); + return; + } this._updatingCamera = true; assert(e.timeStamp !== undefined); @@ -475,7 +496,7 @@ class HandlerManager { if (this._frameId === undefined) { this._frameId = this._map._requestRenderFrame(timeStamp => { delete this._frameId; - this._processInputEvent(new RenderFrameEvent('renderFrame', {timeStamp})); + this.handleEvent(new RenderFrameEvent('renderFrame', {timeStamp})); this._applyChanges(); }); } diff --git a/src/ui/map.js b/src/ui/map.js index cac2ff4b32b..c1e5eee4dad 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -2319,6 +2319,8 @@ class Map extends Camera { } this._renderTaskQueue.clear(); this.painter.destroy(); + this.handlers.destroy(); + delete this.handlers; this.setStyle(null); if (typeof window !== 'undefined') { window.removeEventListener('resize', this._onWindowResize, false);