diff --git a/lib/web_ui/lib/src/engine/window.dart b/lib/web_ui/lib/src/engine/window.dart index 0557acd9c129d..8c7e7c53630ae 100644 --- a/lib/web_ui/lib/src/engine/window.dart +++ b/lib/web_ui/lib/src/engine/window.dart @@ -51,7 +51,7 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow { } BrowserHistory? _browserHistory; - + bool _usingRouter = false; Future _useSingleEntryBrowserHistory() async { if (_browserHistory is SingleEntryBrowserHistory) { return; @@ -61,6 +61,15 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow { _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); } + Future _useMultiEntryBrowserHistory() async { + if (_browserHistory is MultiEntriesBrowserHistory) { + return; + } + final UrlStrategy? strategy = _browserHistory?.urlStrategy; + await _browserHistory?.tearDown(); + _browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy); + } + @visibleForTesting Future debugInitializeHistory( UrlStrategy? strategy, { @@ -68,7 +77,7 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow { }) async { // Prevent any further customization of URL strategy. _isUrlStrategySet = true; - + _usingRouter = false; await _browserHistory?.tearDown(); if (useSingle) { _browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy); @@ -95,11 +104,22 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow { switch (decoded.method) { case 'routeUpdated': - await _useSingleEntryBrowserHistory(); - browserHistory.setRouteName(arguments['routeName']); + if (!_usingRouter) { + await _useSingleEntryBrowserHistory(); + browserHistory.setRouteName(arguments['routeName']); + } else { + assert( + false, + 'Receives old navigator update in a router application. ' + 'This can happen if you use non-router versions of MaterialApp/' + 'CupertinoApp/WidgetsApp together with the router versions of them.' + ); + return false; + } return true; case 'routeInformationUpdated': - assert(browserHistory is MultiEntriesBrowserHistory); + await _useMultiEntryBrowserHistory(); + _usingRouter = true; browserHistory.setRouteName( arguments['location'], state: arguments['state'], diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index 0a02485ef0b8f..676bc1a315023 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. // @dart = 2.6 +import 'dart:async'; import 'dart:html' as html; import 'dart:js_util' as js_util; import 'dart:typed_data'; @@ -69,6 +70,64 @@ void testMain() { expect(window.defaultRouteName, '/'); }); + test('should throw when using nav1 and nav2 together', + () async { + await window.debugInitializeHistory(TestUrlStrategy.fromEntry( + TestHistoryEntry('initial state', null, '/initial'), + ), useSingle: false); + // Receive nav1 update first. + Completer callback = Completer(); + window.sendPlatformMessage( + 'flutter/navigation', + JSONMethodCodec().encodeMethodCall(MethodCall( + 'routeUpdated', + {'routeName': '/bar'}, + )), + (_) { callback.complete(); }, + ); + await callback.future; + expect(window.browserHistory is SingleEntryBrowserHistory, true); + expect(window.browserHistory.urlStrategy.getPath(), '/bar'); + + // We can still receive nav2 update. + callback = Completer(); + window.sendPlatformMessage( + 'flutter/navigation', + JSONMethodCodec().encodeMethodCall(MethodCall( + 'routeInformationUpdated', + { + 'location': '/baz', + 'state': null, + }, + )), + (_) { callback.complete(); }, + ); + await callback.future; + expect(window.browserHistory is MultiEntriesBrowserHistory, true); + expect(window.browserHistory.urlStrategy.getPath(), '/baz'); + + // Throws assertion error if it receives nav1 update after nav2 update. + AssertionError caughtAssertion; + await window.handleNavigationMessage( + JSONMethodCodec().encodeMethodCall(MethodCall( + 'routeUpdated', + {'routeName': '/foo'}, + )) + ).catchError((Object e) { + caughtAssertion = e as AssertionError; + }); + + expect( + caughtAssertion.message, + 'Receives old navigator update in a router application. This can ' + 'happen if you use non-router versions of ' + 'MaterialApp/CupertinoApp/WidgetsApp together with the router versions of them.' + ); + // The history does not change. + expect(window.browserHistory is MultiEntriesBrowserHistory, true); + expect(window.browserHistory.urlStrategy.getPath(), '/baz'); + }); + test('can disable location strategy', () async { // Disable URL strategy. expect(() => jsSetUrlStrategy(null), returnsNormally);