Skip to content

Commit a12309c

Browse files
ashwinuaeHarelM
andauthored
fix: handle undefined projection in setProjection to prevent setState crash (maplibre#7330)
* test: add failing test for setState crash when target style has no projection setState crashes with "Cannot read properties of undefined (reading 'type')" when switching from a style with an explicit projection to one without. The diff() function emits {command: 'setProjection', args: [undefined]}, and setProjection(undefined) dereferences undefined.type. * fix: skip setProjection in setState style diff Projection is owned by the map, not the style. setState should not mutate it as a side effect of diffing two style JSONs. This matches the existing pattern for setCenter/setZoom/setBearing/setPitch/setRoll, which are also skipped during style diffs. * test: update 'do operations if there are changes' test to remove the setProjection spy and projection assignment, since setState no longer processes projection diff commands. * chore: add changelog entry for maplibre#7314 * fix: handle undefined projection in setProjection When setState diffs a style with a projection field against one without, diff() emits setProjection(undefined) which crashes on undefined.type. Default to {type: 'mercator'} when projection is undefined, so setProjection handles a missing value without crashing. * fix: preserve original projection value in stylesheet during setProjection * Apply suggestion from @HarelM --------- Co-authored-by: Harel M <harel.mazor@gmail.com>
1 parent ebee9f7 commit a12309c

3 files changed

Lines changed: 31 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
- _...Add new stuff here..._
44

55
### 🐞 Bug fixes
6-
- _...Add new stuff here..._
76
- Make `fitBounds` and `fitScreenCoordinates` respect the `zoomSnap` map option by snapping the zoom level down to keep bounds fully visible ([#7332](https://github.com/maplibre/maplibre-gl-js/issues/7332) (by [@CommanderStorm](https://github.com/CommanderStorm))
87
- Make `jumpTo`, `easeTo`, and `flyTo` respect the `zoomSnap` map option by snapping the zoom level to the nearest valid increment ([#7333](https://github.com/maplibre/maplibre-gl-js/issues/7333) (by [@CommanderStorm](https://github.com/CommanderStorm))
8+
- Fix `setState` crash when switching styles while globe projection is active ([#7314](https://github.com/maplibre/maplibre-gl-js/issues/7314)) (by [@ashwinuae](https://github.com/ashwinuae))
99

1010
## 5.21.1
1111

src/style/style.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,33 @@ describe('Style.setState', () => {
11031103

11041104
expect(didChange).toBeTruthy();
11051105
});
1106+
1107+
test('setState does not crash when target style has no projection', async () => {
1108+
const style = createStyle();
1109+
const initialStyle = createStyleJSON({
1110+
projection: {type: 'globe'}
1111+
});
1112+
style.loadJSON(initialStyle);
1113+
await style.once('style.load');
1114+
1115+
const targetStyle = createStyleJSON();
1116+
expect(() => style.setState(targetStyle)).not.toThrow();
1117+
});
1118+
1119+
test('setState preserves serialized style when target has no projection', async () => {
1120+
const style = createStyle();
1121+
const initialStyle = createStyleJSON({
1122+
projection: {type: 'globe'}
1123+
});
1124+
style.loadJSON(initialStyle);
1125+
await style.once('style.load');
1126+
1127+
const targetStyle = createStyleJSON();
1128+
style.setState(targetStyle);
1129+
1130+
const serialized = style.serialize();
1131+
expect(serialized).not.toHaveProperty('projection');
1132+
});
11061133
});
11071134

11081135
describe('Style.addSource', () => {

src/style/style.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,15 +1685,16 @@ export class Style extends Evented {
16851685

16861686
setProjection(projection: ProjectionSpecification) {
16871687
this._checkLoaded();
1688+
const resolvedProjection = projection ?? {type: 'mercator'};
16881689
this.stylesheet.projection = projection;
16891690
if (this.projection) {
1690-
if (this.projection.name === projection.type) {
1691+
if (this.projection.name === resolvedProjection.type) {
16911692
return;
16921693
}
16931694
this.projection.destroy();
16941695
delete this.projection;
16951696
}
1696-
this._setProjectionInternal(projection.type);
1697+
this._setProjectionInternal(resolvedProjection.type);
16971698
}
16981699

16991700
getSky(): SkySpecification {

0 commit comments

Comments
 (0)