diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/navigation/history.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/navigation/history.dart index ef660ee1131..2ef9050ffc1 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/navigation/history.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/navigation/history.dart @@ -63,7 +63,7 @@ abstract class BrowserHistory { Object? get currentState => urlStrategy?.getState(); /// Update the url with the given [routeName] and [state]. - void setRouteName(String? routeName, {Object? state}); + void setRouteName(String? routeName, {Object? state, bool replace = false}); /// A callback method to handle browser backward or forward buttons. /// @@ -131,15 +131,23 @@ class MultiEntriesBrowserHistory extends BrowserHistory { } @override - void setRouteName(String? routeName, {Object? state}) { + void setRouteName(String? routeName, {Object? state, bool replace = false}) { if (urlStrategy != null) { assert(routeName != null); - _lastSeenSerialCount += 1; - urlStrategy!.pushState( - _tagWithSerialCount(state, _lastSeenSerialCount), - 'flutter', - routeName!, - ); + if (replace) { + urlStrategy!.replaceState( + _tagWithSerialCount(state, _lastSeenSerialCount), + 'flutter', + routeName!, + ); + } else { + _lastSeenSerialCount += 1; + urlStrategy!.pushState( + _tagWithSerialCount(state, _lastSeenSerialCount), + 'flutter', + routeName!, + ); + } } } @@ -263,7 +271,7 @@ class SingleEntryBrowserHistory extends BrowserHistory { } @override - void setRouteName(String? routeName, {Object? state}) { + void setRouteName(String? routeName, {Object? state, bool replace = false}) { if (urlStrategy != null) { _setupFlutterEntry(urlStrategy!, replace: true, path: routeName); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/window.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/window.dart index 5f355a68ca4..c455a622b03 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/window.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/window.dart @@ -132,6 +132,7 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow { browserHistory.setRouteName( arguments!['location'], state: arguments['state'], + replace: arguments['replace'] ?? false, ); return true; } diff --git a/engine/src/flutter/lib/web_ui/test/window_test.dart b/engine/src/flutter/lib/web_ui/test/window_test.dart index bb823cf7335..9ba60c47981 100644 --- a/engine/src/flutter/lib/web_ui/test/window_test.dart +++ b/engine/src/flutter/lib/web_ui/test/window_test.dart @@ -16,6 +16,13 @@ import 'matchers.dart'; const MethodCodec codec = JSONMethodCodec(); +Map _tagStateWithSerialCount(dynamic state, int serialCount) { + return { + 'serialCount': serialCount, + 'state': state, + }; +} + void main() { internalBootstrapBrowserTest(() => testMain); } @@ -175,6 +182,67 @@ void testMain() { expect(window.browserHistory.urlStrategy!.getPath(), '/foo'); }); + test('can replace in MultiEntriesBrowserHistory', + () async { + await window.debugInitializeHistory(TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'), + ), useSingle: false); + expect(window.browserHistory, isA()); + + Completer callback = Completer(); + window.sendPlatformMessage( + 'flutter/navigation', + JSONMethodCodec().encodeMethodCall(MethodCall( + 'routeInformationUpdated', + { + 'location': '/baz', + 'state': '/state', + }, + )), + (_) { callback.complete(); }, + ); + await callback.future; + expect(window.browserHistory.urlStrategy!.getPath(), '/baz'); + expect(window.browserHistory.urlStrategy!.getState(), _tagStateWithSerialCount('/state', 1)); + + callback = Completer(); + window.sendPlatformMessage( + 'flutter/navigation', + JSONMethodCodec().encodeMethodCall(MethodCall( + 'routeInformationUpdated', + { + 'location': '/baz', + 'state': '/state1', + 'replace': true + }, + )), + (_) { callback.complete(); }, + ); + await callback.future; + expect(window.browserHistory.urlStrategy!.getPath(), '/baz'); + expect(window.browserHistory.urlStrategy!.getState(), _tagStateWithSerialCount('/state1', 1)); + + callback = Completer(); + window.sendPlatformMessage( + 'flutter/navigation', + JSONMethodCodec().encodeMethodCall(MethodCall( + 'routeInformationUpdated', + { + 'location': '/foo', + 'state': '/foostate1', + }, + )), + (_) { callback.complete(); }, + ); + await callback.future; + expect(window.browserHistory.urlStrategy!.getPath(), '/foo'); + expect(window.browserHistory.urlStrategy!.getState(), _tagStateWithSerialCount('/foostate1', 2)); + + await window.browserHistory.back(); + expect(window.browserHistory.urlStrategy!.getPath(), '/baz'); + expect(window.browserHistory.urlStrategy!.getState(), _tagStateWithSerialCount('/state1', 1)); + }); + test('initialize browser history with default url strategy (single)', () async { // On purpose, we don't initialize history on the window. We want to let the // window to self-initialize when it receives a navigation message.