diff --git a/lib/web_ui/lib/src/engine/navigation/url_strategy.dart b/lib/web_ui/lib/src/engine/navigation/url_strategy.dart index 0d49ed64fc495..36b815cbe2bb5 100644 --- a/lib/web_ui/lib/src/engine/navigation/url_strategy.dart +++ b/lib/web_ui/lib/src/engine/navigation/url_strategy.dart @@ -35,7 +35,10 @@ class HashUrlStrategy extends ui_web.UrlStrategy { @override ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) { - final DomEventListener wrappedFn = createDomEventListener(fn); + final DomEventListener wrappedFn = createDomEventListener((DomEvent event) { + // `fn` expects `event.state`, not a `DomEvent`. + fn((event as DomPopStateEvent).state); + }); _platformLocation.addPopStateListener(wrappedFn); return () => _platformLocation.removePopStateListener(wrappedFn); } diff --git a/lib/web_ui/test/engine/history_test.dart b/lib/web_ui/test/engine/history_test.dart index 203ab8b5922cf..d63422a88826a 100644 --- a/lib/web_ui/test/engine/history_test.dart +++ b/lib/web_ui/test/engine/history_test.dart @@ -7,12 +7,16 @@ library; import 'dart:async'; +import 'dart:js_interop' + show JSExportedDartFunction, JSExportedDartFunctionToFunction; import 'package:quiver/testing/async.dart'; import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; -import 'package:ui/src/engine.dart' show DomEventListener, window; +import 'package:ui/src/engine.dart' show window; import 'package:ui/src/engine/browser_detection.dart'; +import 'package:ui/src/engine/dom.dart' + show DomEvent, DomEventListener, createDomPopStateEvent; import 'package:ui/src/engine/navigation.dart'; import 'package:ui/src/engine/services.dart'; import 'package:ui/src/engine/test_embedding.dart'; @@ -647,6 +651,31 @@ void testMain() { location.hash = '#'; expect(strategy.getPath(), '/'); }); + + test('addPopStateListener fn unwraps DomPopStateEvent state', () { + final HashUrlStrategy strategy = HashUrlStrategy(location); + const String expected = 'expected value'; + final List states = []; + + // Put the popStates received from the `location` in a list + strategy.addPopStateListener(states.add); + + // Simulate a popstate with a null state: + location.debugTriggerPopState(null); + + expect(states, hasLength(1)); + expect(states[0], isNull); + + // Simulate a popstate event with `expected` as its 'state'. + location.debugTriggerPopState(expected); + + expect(states, hasLength(2)); + final Object? state = states[1]; + expect(state, isNotNull); + // flutter/flutter#125228 + expect(state, isNot(isA())); + expect(state, expected); + }); }); } @@ -694,15 +723,32 @@ class TestPlatformLocation extends PlatformLocation { @override dynamic state; + List popStateListeners = []; + @override String get pathname => throw UnimplementedError(); @override String get search => throw UnimplementedError(); + /// Calls all the registered `popStateListeners` with a 'popstate' + /// event with value `state` + void debugTriggerPopState(Object? state) { + final DomEvent event = createDomPopStateEvent( + 'popstate', + { + if (state != null) 'state': state, + }, + ); + for (final DomEventListener listener in popStateListeners) { + final Function fn = (listener as JSExportedDartFunction).toDart; + fn(event); + } + } + @override void addPopStateListener(DomEventListener fn) { - throw UnimplementedError(); + popStateListeners.add(fn); } @override