From b65e922874940b3b51cd049351fdfb259d362d6b Mon Sep 17 00:00:00 2001 From: Abdullah <89297042+AzazelSensei@users.noreply.github.com> Date: Thu, 13 Aug 2026 12:51:55 +0300 Subject: [PATCH] fix: stop MarkerLayer wrap loops on a non-finite camera #2178 only rejects a non-finite Marker.point. A NaN camera still makes Rect.overlaps return true for every world copy, so the wrap loops never stop. Skip the layer when the camera is not finite, skip wrapping a marker whose projected point is not finite, and cap world shifts the same way workAcrossWorlds already does. --- lib/src/layer/marker_layer/marker_layer.dart | 37 ++++++++++++++++++-- test/layer/marker_layer_test.dart | 37 ++++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/lib/src/layer/marker_layer/marker_layer.dart b/lib/src/layer/marker_layer/marker_layer.dart index 0e18c700b..2a6c1ae70 100644 --- a/lib/src/layer/marker_layer/marker_layer.dart +++ b/lib/src/layer/marker_layer/marker_layer.dart @@ -98,6 +98,16 @@ class _MarkerLayerState extends State { final pixelOrigin = map.pixelOrigin; final markers = widget.markers; + // A non-finite camera makes Rect.overlaps return true for every world + // copy, so the wrap loops never terminate (OOM/ANR). #2178 only guards + // Marker.point. See https://github.com/fleaflet/flutter_map/issues/2240. + if (!worldWidth.isFinite || + !pixelBounds.left.isFinite || + !pixelBounds.right.isFinite || + !map.rotation.isFinite) { + return const SizedBox.shrink(); + } + return MobileLayerTransformer( child: Stack( children: () sync* { @@ -166,14 +176,37 @@ class _MarkerLayerState extends State { // Repeat over all worlds (<--||-->) until culling determines that // that marker is out of view, and therefore all further markers in - // that direction will also be - if (worldWidth == 0) continue; + // that direction will also be. + // Also skip wrapping when the projected point is non-finite; the + // camera-level guard above does not cover that case. + if (worldWidth == 0 || + !worldWidth.isFinite || + !pxPoint.dx.isFinite || + !pxPoint.dy.isFinite || + !pixelBounds.left.isFinite || + !pixelBounds.right.isFinite) { + continue; + } + + // Same backstop as FeatureLayerUtils.workAcrossWorlds (#2052/#2111). + const maxShiftsCount = 30; + var shiftsCount = 0; for (double shift = -worldWidth;; shift -= worldWidth) { + if (++shiftsCount > maxShiftsCount) { + throw AssertionError( + 'Infinite loop going beyond $maxShiftsCount for world width $worldWidth', + ); + } final additional = getPositioned(shift); if (additional == null) break; yield additional; } for (double shift = worldWidth;; shift += worldWidth) { + if (++shiftsCount > maxShiftsCount) { + throw AssertionError( + 'Infinite loop going beyond $maxShiftsCount for world width $worldWidth', + ); + } final additional = getPositioned(shift); if (additional == null) break; yield additional; diff --git a/test/layer/marker_layer_test.dart b/test/layer/marker_layer_test.dart index 8834c41f5..f4550a8a7 100644 --- a/test/layer/marker_layer_test.dart +++ b/test/layer/marker_layer_test.dart @@ -24,4 +24,41 @@ void main() { expect(find.byType(MarkerLayer), findsWidgets); expect(find.byKey(key), findsOneWidget); }); + + // #2178 only rejects a non-finite Marker.point. A finite marker plus a + // non-finite camera still makes Rect.overlaps return true for every world + // copy, so the wrap loops never stop (OOM / ANR). See #2240. + testWidgets('non-finite camera rotation does not hang MarkerLayer', + (tester) async { + final controller = MapController(); + await tester.pumpWidget( + MaterialApp( + home: FlutterMap( + mapController: controller, + options: const MapOptions( + initialCenter: LatLng(16.6, 120.9), + initialZoom: 12, + ), + children: const [ + MarkerLayer( + markers: [ + Marker( + point: LatLng(16.6, 120.9), + width: 40, + height: 40, + child: SizedBox.shrink(), + ), + ], + ), + ], + ), + ), + ); + + controller.rotate(double.nan); + await tester.pump(); + + expect(find.byType(FlutterMap), findsOneWidget); + expect(find.byType(MarkerLayer), findsOneWidget); + }); }