@@ -22,55 +22,37 @@ part of engine;
2222/// * [MultiEntriesBrowserHistory] : which creates a set of states that records
2323/// the navigating events happened in the framework.
2424abstract class BrowserHistory {
25+ static BrowserHistory defaultImpl ({required UrlStrategy ? urlStrategy}) {
26+ return MultiEntriesBrowserHistory (urlStrategy: urlStrategy);
27+ }
28+
2529 late ui.VoidCallback _unsubscribe;
2630
2731 /// The strategy to interact with html browser history.
28- JsUrlStrategy ? get urlStrategy => _urlStrategy;
29- JsUrlStrategy ? _urlStrategy;
30- /// Updates the strategy.
31- ///
32- /// This method will also remove any previous modifications to the html
33- /// browser history and start anew.
34- Future <void > setUrlStrategy (JsUrlStrategy ? strategy) async {
35- if (strategy != _urlStrategy) {
36- await _tearoffStrategy (_urlStrategy);
37- _urlStrategy = strategy;
38- await _setupStrategy (_urlStrategy);
39- }
40- }
41-
42- Future <void > _setupStrategy (JsUrlStrategy ? strategy) async {
43- if (strategy == null ) {
44- return ;
45- }
46- _unsubscribe = strategy.onPopState (onPopState as dynamic Function (html.Event ));
47- await setup ();
48- }
32+ UrlStrategy ? get urlStrategy;
4933
50- Future <void > _tearoffStrategy (JsUrlStrategy ? strategy) async {
51- if (strategy == null ) {
52- return ;
53- }
54- _unsubscribe ();
34+ bool _isDisposed = false ;
5535
56- await tearDown ();
36+ void _setupStrategy (UrlStrategy strategy) {
37+ _unsubscribe = strategy.addPopStateListener (
38+ onPopState as html.EventListener ,
39+ );
5740 }
5841
5942 /// Exit this application and return to the previous page.
6043 Future <void > exit () async {
61- if (_urlStrategy != null ) {
62- await _tearoffStrategy (_urlStrategy );
44+ if (urlStrategy != null ) {
45+ await tearDown ( );
6346 // Now the history should be in the original state, back one more time to
6447 // exit the application.
65- await _urlStrategy! .go (- 1 );
66- _urlStrategy = null ;
48+ await urlStrategy! .go (- 1 );
6749 }
6850 }
6951
7052 /// This method does the same thing as the browser back button.
7153 Future <void > back () {
72- if (_urlStrategy != null ) {
73- return _urlStrategy ! .go (- 1 );
54+ if (urlStrategy != null ) {
55+ return urlStrategy ! .go (- 1 );
7456 }
7557 return Future <void >.value ();
7658 }
@@ -79,23 +61,20 @@ abstract class BrowserHistory {
7961 String get currentPath => urlStrategy? .getPath () ?? '/' ;
8062
8163 /// The state of the current location of the user's browser.
82- dynamic get currentState => urlStrategy? .getState ();
64+ Object ? get currentState => urlStrategy? .getState ();
8365
8466 /// Update the url with the given [routeName] and [state] .
85- void setRouteName (String ? routeName, {dynamic ? state});
67+ void setRouteName (String ? routeName, {Object ? state});
8668
8769 /// A callback method to handle browser backward or forward buttons.
8870 ///
8971 /// Subclasses should send appropriate system messages to update the flutter
9072 /// applications accordingly.
9173 void onPopState (covariant html.PopStateEvent event);
9274
93- /// Sets up any prerequisites to use this browser history class.
94- Future <void > setup () => Future <void >.value ();
95-
9675 /// Restore any modifications to the html browser history during the lifetime
9776 /// of this class.
98- Future <void > tearDown () => Future < void >. value () ;
77+ Future <void > tearDown ();
9978}
10079
10180/// A browser history class that creates a set of browser history entries to
@@ -113,27 +92,47 @@ abstract class BrowserHistory {
11392/// * [SingleEntryBrowserHistory] , which is used when the framework does not use
11493/// a Router for routing.
11594class MultiEntriesBrowserHistory extends BrowserHistory {
95+ MultiEntriesBrowserHistory ({required this .urlStrategy}) {
96+ final UrlStrategy ? strategy = urlStrategy;
97+ if (strategy == null ) {
98+ return ;
99+ }
100+
101+ _setupStrategy (strategy);
102+ if (! _hasSerialCount (currentState)) {
103+ strategy.replaceState (
104+ _tagWithSerialCount (currentState, 0 ), 'flutter' , currentPath);
105+ }
106+ // If we restore from a page refresh, the _currentSerialCount may not be 0.
107+ _lastSeenSerialCount = _currentSerialCount;
108+ }
109+
110+ @override
111+ final UrlStrategy ? urlStrategy;
112+
116113 late int _lastSeenSerialCount;
117114 int get _currentSerialCount {
118115 if (_hasSerialCount (currentState)) {
119- return currentState['serialCount' ] as int ;
116+ final Map <dynamic , dynamic > stateMap =
117+ currentState as Map <dynamic , dynamic >;
118+ return stateMap['serialCount' ] as int ;
120119 }
121120 return 0 ;
122121 }
123122
124- dynamic _tagWithSerialCount (dynamic originialState, int count) {
125- return < dynamic , dynamic > {
123+ Object ? _tagWithSerialCount (Object ? originialState, int count) {
124+ return < dynamic , dynamic > {
126125 'serialCount' : count,
127126 'state' : originialState,
128127 };
129128 }
130129
131- bool _hasSerialCount (dynamic state) {
130+ bool _hasSerialCount (Object ? state) {
132131 return state is Map && state['serialCount' ] != null ;
133132 }
134133
135134 @override
136- void setRouteName (String ? routeName, {dynamic ? state}) {
135+ void setRouteName (String ? routeName, {Object ? state}) {
137136 if (urlStrategy != null ) {
138137 assert (routeName != null );
139138 _lastSeenSerialCount += 1 ;
@@ -154,41 +153,32 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
154153 // In this case we assume this will be the next history entry from the
155154 // last seen entry.
156155 urlStrategy! .replaceState (
157- _tagWithSerialCount (event.state, _lastSeenSerialCount + 1 ),
158- 'flutter' ,
159- currentPath);
156+ _tagWithSerialCount (event.state, _lastSeenSerialCount + 1 ),
157+ 'flutter' ,
158+ currentPath);
160159 }
161160 _lastSeenSerialCount = _currentSerialCount;
162161 if (window._onPlatformMessage != null ) {
163162 window.invokeOnPlatformMessage (
164163 'flutter/navigation' ,
165164 const JSONMethodCodec ().encodeMethodCall (
166- MethodCall ('pushRouteInformation' , < dynamic , dynamic > {
167- 'location' : currentPath,
168- 'state' : event.state? ['state' ],
169- })
170- ),
165+ MethodCall ('pushRouteInformation' , < dynamic , dynamic > {
166+ 'location' : currentPath,
167+ 'state' : event.state? ['state' ],
168+ })),
171169 (_) {},
172170 );
173171 }
174172 }
175173
176174 @override
177- Future <void > setup () {
178- if (! _hasSerialCount (currentState)) {
179- urlStrategy! .replaceState (
180- _tagWithSerialCount (currentState, 0 ),
181- 'flutter' ,
182- currentPath
183- );
175+ Future <void > tearDown () async {
176+ if (_isDisposed || urlStrategy == null ) {
177+ return ;
184178 }
185- // If we retore from a page refresh, the _currentSerialCount may not be 0.
186- _lastSeenSerialCount = _currentSerialCount;
187- return Future <void >.value ();
188- }
179+ _isDisposed = true ;
180+ _unsubscribe ();
189181
190- @override
191- Future <void > tearDown () async {
192182 // Restores the html browser history.
193183 assert (_hasSerialCount (currentState));
194184 int backCount = _currentSerialCount;
@@ -197,8 +187,10 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
197187 }
198188 // Unwrap state.
199189 assert (_hasSerialCount (currentState) && _currentSerialCount == 0 );
190+ final Map <dynamic , dynamic > stateMap =
191+ currentState as Map <dynamic , dynamic >;
200192 urlStrategy! .replaceState (
201- currentState ['state' ],
193+ stateMap ['state' ],
202194 'flutter' ,
203195 currentPath,
204196 );
@@ -222,35 +214,60 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
222214/// * [MultiEntriesBrowserHistory] , which is used when the framework uses a
223215/// Router for routing.
224216class SingleEntryBrowserHistory extends BrowserHistory {
217+ SingleEntryBrowserHistory ({required this .urlStrategy}) {
218+ final UrlStrategy ? strategy = urlStrategy;
219+ if (strategy == null ) {
220+ return ;
221+ }
222+
223+ _setupStrategy (strategy);
224+
225+ final String path = currentPath;
226+ if (_isFlutterEntry (html.window.history.state)) {
227+ // This could happen if the user, for example, refreshes the page. They
228+ // will land directly on the "flutter" entry, so there's no need to setup
229+ // the "origin" and "flutter" entries, we can safely assume they are
230+ // already setup.
231+ } else {
232+ _setupOriginEntry (strategy);
233+ _setupFlutterEntry (strategy, replace: false , path: path);
234+ }
235+ }
236+
237+ @override
238+ final UrlStrategy ? urlStrategy;
239+
225240 static const MethodCall _popRouteMethodCall = MethodCall ('popRoute' );
226241 static const String _kFlutterTag = 'flutter' ;
227242 static const String _kOriginTag = 'origin' ;
228243
229- Map <String , dynamic > _wrapOriginState (dynamic state) {
244+ Map <String , dynamic > _wrapOriginState (Object ? state) {
230245 return < String , dynamic > {_kOriginTag: true , 'state' : state};
231246 }
232- dynamic _unwrapOriginState (dynamic state) {
247+
248+ Object ? _unwrapOriginState (Object ? state) {
233249 assert (_isOriginEntry (state));
234250 final Map <dynamic , dynamic > originState = state as Map <dynamic , dynamic >;
235251 return originState['state' ];
236252 }
253+
237254 Map <String , bool > _flutterState = < String , bool > {_kFlutterTag: true };
238255
239256 /// The origin entry is the history entry that the Flutter app landed on. It's
240257 /// created by the browser when the user navigates to the url of the app.
241- bool _isOriginEntry (dynamic state) {
258+ bool _isOriginEntry (Object ? state) {
242259 return state is Map && state[_kOriginTag] == true ;
243260 }
244261
245262 /// The flutter entry is a history entry that we maintain on top of the origin
246263 /// entry. It allows us to catch popstate events when the user hits the back
247264 /// button.
248- bool _isFlutterEntry (dynamic state) {
265+ bool _isFlutterEntry (Object ? state) {
249266 return state is Map && state[_kFlutterTag] == true ;
250267 }
251268
252269 @override
253- void setRouteName (String ? routeName, {dynamic ? state}) {
270+ void setRouteName (String ? routeName, {Object ? state}) {
254271 if (urlStrategy != null ) {
255272 _setupFlutterEntry (urlStrategy! , replace: true , path: routeName);
256273 }
@@ -260,7 +277,7 @@ class SingleEntryBrowserHistory extends BrowserHistory {
260277 @override
261278 void onPopState (covariant html.PopStateEvent event) {
262279 if (_isOriginEntry (event.state)) {
263- _setupFlutterEntry (_urlStrategy ! );
280+ _setupFlutterEntry (urlStrategy ! );
264281
265282 // 2. Send a 'popRoute' platform message so the app can handle it accordingly.
266283 if (window._onPlatformMessage != null ) {
@@ -302,22 +319,22 @@ class SingleEntryBrowserHistory extends BrowserHistory {
302319 // 2. Then we remove the new entry.
303320 // This will take us back to our "flutter" entry and it causes a new
304321 // popstate event that will be handled in the "else if" section above.
305- _urlStrategy ! .go (- 1 );
322+ urlStrategy ! .go (- 1 );
306323 }
307324 }
308325
309326 /// This method should be called when the Origin Entry is active. It just
310327 /// replaces the state of the entry so that we can recognize it later using
311328 /// [_isOriginEntry] inside [_popStateListener] .
312- void _setupOriginEntry (JsUrlStrategy strategy) {
329+ void _setupOriginEntry (UrlStrategy strategy) {
313330 assert (strategy != null ); // ignore: unnecessary_null_comparison
314331 strategy.replaceState (_wrapOriginState (currentState), 'origin' , '' );
315332 }
316333
317334 /// This method is used manipulate the Flutter Entry which is always the
318335 /// active entry while the Flutter app is running.
319336 void _setupFlutterEntry (
320- JsUrlStrategy strategy, {
337+ UrlStrategy strategy, {
321338 bool replace = false ,
322339 String ? path,
323340 }) {
@@ -330,28 +347,18 @@ class SingleEntryBrowserHistory extends BrowserHistory {
330347 }
331348 }
332349
333- @override
334- Future <void > setup () {
335- final String path = currentPath;
336- if (_isFlutterEntry (html.window.history.state)) {
337- // This could happen if the user, for example, refreshes the page. They
338- // will land directly on the "flutter" entry, so there's no need to setup
339- // the "origin" and "flutter" entries, we can safely assume they are
340- // already setup.
341- } else {
342- _setupOriginEntry (urlStrategy! );
343- _setupFlutterEntry (urlStrategy! , replace: false , path: path);
344- }
345- return Future <void >.value ();
346- }
347-
348350 @override
349351 Future <void > tearDown () async {
350- if (urlStrategy != null ) {
351- // We need to remove the flutter entry that we pushed in setup.
352- await urlStrategy! .go (- 1 );
353- // Restores original state.
354- urlStrategy! .replaceState (_unwrapOriginState (currentState), 'flutter' , currentPath);
352+ if (_isDisposed || urlStrategy == null ) {
353+ return ;
355354 }
355+ _isDisposed = true ;
356+ _unsubscribe ();
357+
358+ // We need to remove the flutter entry that we pushed in setup.
359+ await urlStrategy! .go (- 1 );
360+ // Restores original state.
361+ urlStrategy!
362+ .replaceState (_unwrapOriginState (currentState), 'flutter' , currentPath);
356363 }
357364}
0 commit comments