From afc4556ada9288a8e87a4695d91ebb0e28b2589a Mon Sep 17 00:00:00 2001
From: Anna Peery <42715836+avpeery@users.noreply.github.com>
Date: Thu, 7 Oct 2021 11:36:01 -0700
Subject: [PATCH] Fix an iOS15 issue where Safari tab bar interrupts panning
 (#11084) (#11101)

* Fix an iOS15 issue where Safari tab bar interrupts panning (#11084) (#11089)

* fix an iOS15 issue where map stops when panning

* fix tests and lint

* Test drag pan handler does not end interaction on resize

* Move blur event reset into non-touch handlers (#11087)

* Move blur event reset into non-touch handlers

* Fix linter

* Fix/amend unit tests

* Flush task queue in rotate test

Co-authored-by: Ricky Reusser <ricky.reusser@mapbox.com>
Co-authored-by: Ricky Reusser <rreusser@users.noreply.github.com>

Co-authored-by: Vladimir Agafonkin <agafonkin@gmail.com>

* Removed getBoundingClientRect conflict for unit tests

* Added offsetHeight to replace getBoundingClientRect to fix unit tests

* add clientWidth and clientHeight to unit tests

* container -> map.getContainer()

* Replaced offsetWidth with clientWidth

* removing change difference in attribution and logo unit tests from v1.13.2

* Change size of container instead of canvas container to trigger resize in unit tests

* Added change in height to trigger resize

* fix to attribution.test.js

* Fixes logo.test.js to pass

* removed unneeded changes

Co-authored-by: Ricky Reusser <rreusser@users.noreply.github.com>
Co-authored-by: Vladimir Agafonkin <agafonkin@gmail.com>
---
 src/ui/handler/box_zoom.js               |  4 +++
 src/ui/handler/click_zoom.js             |  4 +++
 src/ui/handler/keyboard.js               |  4 +++
 src/ui/handler/mouse.js                  |  4 +++
 src/ui/handler/scroll_zoom.js            |  4 +++
 src/ui/handler_manager.js                |  5 ----
 src/ui/map.js                            |  8 ++---
 test/unit/ui/control/attribution.test.js |  2 ++
 test/unit/ui/control/logo.test.js        |  2 ++
 test/unit/ui/handler/drag_pan.test.js    | 37 +++++++++++++++++++++++-
 test/unit/ui/handler/drag_rotate.test.js |  2 ++
 test/unit/ui/map.test.js                 | 32 ++++++++++++++++++++
 12 files changed, 98 insertions(+), 10 deletions(-)

diff --git a/src/ui/handler/box_zoom.js b/src/ui/handler/box_zoom.js
index be8415ab7e3..c004e3637d0 100644
--- a/src/ui/handler/box_zoom.js
+++ b/src/ui/handler/box_zoom.js
@@ -142,6 +142,10 @@ class BoxZoomHandler {
         }
     }
 
+    blur() {
+        this.reset();
+    }
+
     reset() {
         this._active = false;
 
diff --git a/src/ui/handler/click_zoom.js b/src/ui/handler/click_zoom.js
index 822de909395..2d8f5662b7f 100644
--- a/src/ui/handler/click_zoom.js
+++ b/src/ui/handler/click_zoom.js
@@ -16,6 +16,10 @@ export default class ClickZoomHandler {
         this._active = false;
     }
 
+    blur() {
+        this.reset();
+    }
+
     dblclick(e: MouseEvent, point: Point) {
         e.preventDefault();
         return {
diff --git a/src/ui/handler/keyboard.js b/src/ui/handler/keyboard.js
index faf22a172d2..15de2d54153 100644
--- a/src/ui/handler/keyboard.js
+++ b/src/ui/handler/keyboard.js
@@ -41,6 +41,10 @@ class KeyboardHandler {
         this._rotationDisabled = false;
     }
 
+    blur() {
+        this.reset();
+    }
+
     reset() {
         this._active = false;
     }
diff --git a/src/ui/handler/mouse.js b/src/ui/handler/mouse.js
index 3cf5c532816..55b235fe39f 100644
--- a/src/ui/handler/mouse.js
+++ b/src/ui/handler/mouse.js
@@ -31,6 +31,10 @@ class MouseHandler {
         this._clickTolerance = options.clickTolerance || 1;
     }
 
+    blur() {
+        this.reset();
+    }
+
     reset() {
         this._active = false;
         this._moved = false;
diff --git a/src/ui/handler/scroll_zoom.js b/src/ui/handler/scroll_zoom.js
index 1ae4553487f..40eb32a89d4 100644
--- a/src/ui/handler/scroll_zoom.js
+++ b/src/ui/handler/scroll_zoom.js
@@ -338,6 +338,10 @@ class ScrollZoomHandler {
         return easing;
     }
 
+    blur() {
+        this.reset();
+    }
+
     reset() {
         this._active = false;
     }
diff --git a/src/ui/handler_manager.js b/src/ui/handler_manager.js
index 56d538443c4..4b4d2d9f046 100644
--- a/src/ui/handler_manager.js
+++ b/src/ui/handler_manager.js
@@ -291,11 +291,6 @@ class HandlerManager {
 
     handleEvent(e: InputEvent | RenderFrameEvent, eventName?: string) {
 
-        if (e.type === 'blur') {
-            this.stop(true);
-            return;
-        }
-
         this._updatingCamera = true;
         assert(e.timeStamp !== undefined);
 
diff --git a/src/ui/map.js b/src/ui/map.js
index 851236d15bb..e8321037778 100755
--- a/src/ui/map.js
+++ b/src/ui/map.js
@@ -591,9 +591,10 @@ class Map extends Camera {
      * if (mapDiv.style.visibility === true) map.resize();
      */
     resize(eventData?: Object) {
-        const dimensions = this._containerDimensions();
-        const width = dimensions[0];
-        const height = dimensions[1];
+        const [width, height] = this._containerDimensions();
+
+        // do nothing if container remained the same size
+        if (width === this.transform.width && height === this.transform.height) return this;
 
         this._resizeCanvas(width, height);
         this.transform.resize(width, height);
@@ -601,7 +602,6 @@ class Map extends Camera {
 
         const fireMoving = !this._moving;
         if (fireMoving) {
-            this.stop();
             this.fire(new Event('movestart', eventData))
                 .fire(new Event('move', eventData));
         }
diff --git a/test/unit/ui/control/attribution.test.js b/test/unit/ui/control/attribution.test.js
index 99cd411ea52..ed6ae6e882d 100644
--- a/test/unit/ui/control/attribution.test.js
+++ b/test/unit/ui/control/attribution.test.js
@@ -63,6 +63,7 @@ test('AttributionControl appears in compact mode if compact option is used', (t)
 test('AttributionControl appears in compact mode if container is less then 640 pixel wide', (t) => {
     const map = createMap(t);
     Object.defineProperty(map.getCanvasContainer(), 'offsetWidth', {value: 700, configurable: true});
+    Object.defineProperty(map.getContainer(), 'clientWidth', {value: 700, configurable: true});
     map.addControl(new AttributionControl());
 
     const container = map.getContainer();
@@ -70,6 +71,7 @@ test('AttributionControl appears in compact mode if container is less then 640 p
     t.equal(container.querySelectorAll('.mapboxgl-ctrl-attrib:not(.mapboxgl-compact)').length, 1);
 
     Object.defineProperty(map.getCanvasContainer(), 'offsetWidth', {value: 600, configurable: true});
+    Object.defineProperty(map.getContainer(), 'clientWidth', {value: 600, configurable: true});
     map.resize();
 
     t.equal(container.querySelectorAll('.mapboxgl-ctrl-attrib.mapboxgl-compact').length, 1);
diff --git a/test/unit/ui/control/logo.test.js b/test/unit/ui/control/logo.test.js
index 0b271b4a6cf..749985137ba 100644
--- a/test/unit/ui/control/logo.test.js
+++ b/test/unit/ui/control/logo.test.js
@@ -93,10 +93,12 @@ test('LogoControl appears in compact mode if container is less then 250 pixel wi
     const container = map.getContainer();
 
     Object.defineProperty(map.getCanvasContainer(), 'offsetWidth', {value: 255, configurable: true});
+    Object.defineProperty(map.getContainer(), 'clientWidth', {value: 255, configurable: true});
     map.resize();
     t.equal(container.querySelectorAll('.mapboxgl-ctrl-logo:not(.mapboxgl-compact)').length, 1);
 
     Object.defineProperty(map.getCanvasContainer(), 'offsetWidth', {value: 245, configurable: true});
+    Object.defineProperty(map.getContainer(), 'clientWidth', {value: 245, configurable: true});
     map.resize();
     t.equal(container.querySelectorAll('.mapboxgl-ctrl-logo.mapboxgl-compact').length, 1);
 
diff --git a/test/unit/ui/handler/drag_pan.test.js b/test/unit/ui/handler/drag_pan.test.js
index 726900e9e82..32f95555d51 100644
--- a/test/unit/ui/handler/drag_pan.test.js
+++ b/test/unit/ui/handler/drag_pan.test.js
@@ -150,13 +150,15 @@ test('DragPanHandler ends a mouse-triggered drag if the window blurs', (t) => {
     map._renderTaskQueue.run();
 
     simulate.blur(window);
+    map._renderTaskQueue.run();
+
     t.equal(dragend.callCount, 1);
 
     map.remove();
     t.end();
 });
 
-test('DragPanHandler ends a touch-triggered drag if the window blurs', (t) => {
+test('DragPanHandler does not end a touch-triggered drag if the window blurs', (t) => {
     const map = createMap(t);
     const target = map.getCanvas();
 
@@ -170,7 +172,40 @@ test('DragPanHandler ends a touch-triggered drag if the window blurs', (t) => {
     map._renderTaskQueue.run();
 
     simulate.blur(window);
+    map._renderTaskQueue.run();
+
+    t.equal(dragend.callCount, 0);
+
+    map.remove();
+    t.end();
+});
+
+test('DragPanHandler does not end a touch-triggered drag if the window resizes', (t) => {
+    const map = createMap(t);
+    const target = map.getCanvas();
+
+    const dragend = t.spy();
+    map.on('dragend', dragend);
+
+    const drag = t.spy();
+    map.on('drag', drag);
+
+    simulate.touchstart(map.getCanvas(), {touches: [{target, clientX: 0, clientY: 0}]});
+    map._renderTaskQueue.run();
+
+    simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 10, clientY: 10}]});
+    map._renderTaskQueue.run();
+
+    map.resize();
+
+    simulate.touchmove(map.getCanvas(), {touches: [{target, clientX: 20, clientY: 10}]});
+    map._renderTaskQueue.run();
+
+    simulate.touchend(map.getCanvas());
+    map._renderTaskQueue.run();
+
     t.equal(dragend.callCount, 1);
+    t.equal(drag.callCount, 2);
 
     map.remove();
     t.end();
diff --git a/test/unit/ui/handler/drag_rotate.test.js b/test/unit/ui/handler/drag_rotate.test.js
index f5dd762e084..3ce816d1f11 100644
--- a/test/unit/ui/handler/drag_rotate.test.js
+++ b/test/unit/ui/handler/drag_rotate.test.js
@@ -491,6 +491,8 @@ test('DragRotateHandler ends rotation if the window blurs (#3389)', (t) => {
     t.equal(rotate.callCount, 1);
 
     simulate.blur(window);
+    map._renderTaskQueue.run();
+
     t.equal(rotateend.callCount, 1);
 
     map.remove();
diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js
index 159597fcb52..8695831c520 100755
--- a/test/unit/ui/map.test.js
+++ b/test/unit/ui/map.test.js
@@ -534,10 +534,42 @@ test('Map', (t) => {
             t.end();
         });
 
+        t.test('does nothing if container size is the same', (t) => {
+            const map = createMap(t);
+
+            t.spy(map.transform, 'resize');
+            t.spy(map.painter, 'resize');
+
+            map.resize();
+
+            t.notOk(map.transform.resize.called);
+            t.notOk(map.painter.resize.called);
+
+            t.end();
+        });
+
+        t.test('does not call stop on resize', (t) => {
+            const map = createMap(t);
+
+            Object.defineProperty(map.getContainer(), 'clientWidth', {value: 250});
+            Object.defineProperty(map.getContainer(), 'clientHeight', {value: 250});
+
+            t.spy(map, 'stop');
+
+            map.resize();
+
+            t.notOk(map.stop.called);
+
+            t.end();
+        });
+
         t.test('fires movestart, move, resize, and moveend events', (t) => {
             const map = createMap(t),
                 events = [];
 
+            Object.defineProperty(map.getContainer(), 'clientWidth', {value: 250});
+            Object.defineProperty(map.getContainer(), 'clientHeight', {value: 250});
+
             ['movestart', 'move', 'resize', 'moveend'].forEach((event) => {
                 map.on(event, (e) => {
                     events.push(e.type);