diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b0ee01fd4..6a4e73d705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Fix geometry artifacts when globe terrain is zoomed out too much ([#5232](https://github.com/maplibre/maplibre-gl-js/pull/5232)) - Fix center being incorrectly constrained when using globe ([#5186](https://github.com/maplibre/maplibre-gl-js/pull/5186)) - Fix atmosphere improperly blending into the background ([#5235](https://github.com/maplibre/maplibre-gl-js/pull/5235)) +- Fix parsing wrong hash location ([#5131](https://github.com/maplibre/maplibre-gl-js/pull/5131)) - _...Add new stuff here..._ ## 5.0.0-pre.10 diff --git a/src/ui/hash.test.ts b/src/ui/hash.test.ts index d7fc8911bd..b5bfe25a32 100644 --- a/src/ui/hash.test.ts +++ b/src/ui/hash.test.ts @@ -111,10 +111,6 @@ describe('hash', () => { window.location.hash = '#4/wrongly/formed/hash'; expect(hash._onHashChange()).toBeFalsy(); - - window.location.hash = '#map=10/3.00/-1.00&foo=bar'; - - expect(hash._onHashChange()).toBeFalsy(); }); test('#_onHashChange empty', () => { @@ -329,6 +325,55 @@ describe('hash', () => { expect(window.location.hash).toBe('#baz&foo=bar'); }); + describe('#_isValidHash', () => { + let hash: Hash + + beforeEach(() => { + hash = createHash() + .addTo(map); + }) + + test('validate correct hash', () => { + window.location.hash = '#10/3.00/-1.00'; + + expect(hash._isValidHash(hash._getCurrentHash())).toBeTruthy(); + + window.location.hash = '#5/1.00/0.50/30/60'; + + expect(hash._isValidHash(hash._getCurrentHash())).toBeTruthy(); + }); + + test('invalidate hash with string values', () => { + window.location.hash = '#4/wrongly/formed/hash'; + + expect(hash._isValidHash(hash._getCurrentHash())).toBeFalsy(); + }); + + test('invalidate hash that is named, but should not be', () => { + window.location.hash = '#map=10/3.00/-1.00&foo=bar'; + + expect(hash._isValidHash(hash._getCurrentHash())).toBeFalsy(); + }); + + test('invalidate hash, zoom greater than maxZoom', () => { + window.location.hash = '#24/3.00/-1.00'; + + expect(hash._isValidHash(hash._getCurrentHash())).toBeFalsy(); + }); + + test('invalidate hash, latitude out of range', () => { + window.location.hash = '#10/100.00/-1.00'; + + expect(hash._isValidHash(hash._getCurrentHash())).toBeFalsy(); + }); + + test('invalidate hash, pitch greater than maxPitch', () => { + window.location.hash = '#10/3.00/-1.00/30/90'; + + expect(hash._isValidHash(hash._getCurrentHash())).toBeFalsy(); + }); + }); + test('initialize http://localhost/#', () => { window.location.href = 'http://localhost/#'; createHash().addTo(map); diff --git a/src/ui/hash.ts b/src/ui/hash.ts index b647205520..fc817eb4e3 100644 --- a/src/ui/hash.ts +++ b/src/ui/hash.ts @@ -1,4 +1,5 @@ import {throttle} from '../util/throttle'; +import {LngLat} from '../geo/lng_lat'; import type {Map} from './map'; @@ -102,18 +103,21 @@ export class Hash { }; _onHashChange = () => { - const loc = this._getCurrentHash(); - if (loc.length >= 3 && !loc.some(v => isNaN(v))) { - const bearing = this._map.dragRotate.isEnabled() && this._map.touchZoomRotate.isEnabled() ? +(loc[3] || 0) : this._map.getBearing(); - this._map.jumpTo({ - center: [+loc[2], +loc[1]], - zoom: +loc[0], - bearing, - pitch: +(loc[4] || 0) - }); - return true; + const hash = this._getCurrentHash(); + + if (!this._isValidHash(hash)) { + return false; } - return false; + + const bearing = this._map.dragRotate.isEnabled() && this._map.touchZoomRotate.isEnabled() ? +(hash[3] || 0) : this._map.getBearing(); + this._map.jumpTo({ + center: [+hash[2], +hash[1]], + zoom: +hash[0], + bearing, + pitch: +(hash[4] || 0) + }); + + return true; }; _updateHashUnthrottled = () => { @@ -150,4 +154,25 @@ export class Hash { * Mobile Safari doesn't allow updating the hash more than 100 times per 30 seconds. */ _updateHash: () => ReturnType = throttle(this._updateHashUnthrottled, 30 * 1000 / 100); + + _isValidHash(hash: number[]) { + if (hash.length < 3 || hash.some(isNaN)) { + return false; + } + + // LngLat() throws error if latitude is out of range, and it's valid if it succeeds. + try { + new LngLat(+hash[2], +hash[1]); + } catch { + return false; + } + + const zoom = +hash[0]; + const bearing = +(hash[3] || 0); + const pitch = +(hash[4] || 0); + + return zoom >= this._map.getMinZoom() && zoom <= this._map.getMaxZoom() && + bearing >= 0 && bearing <= 180 && + pitch >= this._map.getMinPitch() && pitch <= this._map.getMaxPitch(); + }; }