@@ -22,55 +22,37 @@ part of engine;
22
22
/// * [MultiEntriesBrowserHistory] : which creates a set of states that records
23
23
/// the navigating events happened in the framework.
24
24
abstract class BrowserHistory {
25
+ static BrowserHistory defaultImpl ({required UrlStrategy ? urlStrategy}) {
26
+ return MultiEntriesBrowserHistory (urlStrategy: urlStrategy);
27
+ }
28
+
25
29
late ui.VoidCallback _unsubscribe;
26
30
27
31
/// 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;
49
33
50
- Future <void > _tearoffStrategy (JsUrlStrategy ? strategy) async {
51
- if (strategy == null ) {
52
- return ;
53
- }
54
- _unsubscribe ();
34
+ bool _isDisposed = false ;
55
35
56
- await tearDown ();
36
+ void _setupStrategy (UrlStrategy strategy) {
37
+ _unsubscribe = strategy.addPopStateListener (
38
+ onPopState as html.EventListener ,
39
+ );
57
40
}
58
41
59
42
/// Exit this application and return to the previous page.
60
43
Future <void > exit () async {
61
- if (_urlStrategy != null ) {
62
- await _tearoffStrategy (_urlStrategy );
44
+ if (urlStrategy != null ) {
45
+ await tearDown ( );
63
46
// Now the history should be in the original state, back one more time to
64
47
// exit the application.
65
- await _urlStrategy! .go (- 1 );
66
- _urlStrategy = null ;
48
+ await urlStrategy! .go (- 1 );
67
49
}
68
50
}
69
51
70
52
/// This method does the same thing as the browser back button.
71
53
Future <void > back () {
72
- if (_urlStrategy != null ) {
73
- return _urlStrategy ! .go (- 1 );
54
+ if (urlStrategy != null ) {
55
+ return urlStrategy ! .go (- 1 );
74
56
}
75
57
return Future <void >.value ();
76
58
}
@@ -79,23 +61,20 @@ abstract class BrowserHistory {
79
61
String get currentPath => urlStrategy? .getPath () ?? '/' ;
80
62
81
63
/// The state of the current location of the user's browser.
82
- dynamic get currentState => urlStrategy? .getState ();
64
+ Object ? get currentState => urlStrategy? .getState ();
83
65
84
66
/// Update the url with the given [routeName] and [state] .
85
- void setRouteName (String ? routeName, {dynamic ? state});
67
+ void setRouteName (String ? routeName, {Object ? state});
86
68
87
69
/// A callback method to handle browser backward or forward buttons.
88
70
///
89
71
/// Subclasses should send appropriate system messages to update the flutter
90
72
/// applications accordingly.
91
73
void onPopState (covariant html.PopStateEvent event);
92
74
93
- /// Sets up any prerequisites to use this browser history class.
94
- Future <void > setup () => Future <void >.value ();
95
-
96
75
/// Restore any modifications to the html browser history during the lifetime
97
76
/// of this class.
98
- Future <void > tearDown () => Future < void >. value () ;
77
+ Future <void > tearDown ();
99
78
}
100
79
101
80
/// A browser history class that creates a set of browser history entries to
@@ -113,27 +92,47 @@ abstract class BrowserHistory {
113
92
/// * [SingleEntryBrowserHistory] , which is used when the framework does not use
114
93
/// a Router for routing.
115
94
class 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
+
116
113
late int _lastSeenSerialCount;
117
114
int get _currentSerialCount {
118
115
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 ;
120
119
}
121
120
return 0 ;
122
121
}
123
122
124
- dynamic _tagWithSerialCount (dynamic originialState, int count) {
125
- return < dynamic , dynamic > {
123
+ Object ? _tagWithSerialCount (Object ? originialState, int count) {
124
+ return < dynamic , dynamic > {
126
125
'serialCount' : count,
127
126
'state' : originialState,
128
127
};
129
128
}
130
129
131
- bool _hasSerialCount (dynamic state) {
130
+ bool _hasSerialCount (Object ? state) {
132
131
return state is Map && state['serialCount' ] != null ;
133
132
}
134
133
135
134
@override
136
- void setRouteName (String ? routeName, {dynamic ? state}) {
135
+ void setRouteName (String ? routeName, {Object ? state}) {
137
136
if (urlStrategy != null ) {
138
137
assert (routeName != null );
139
138
_lastSeenSerialCount += 1 ;
@@ -154,41 +153,32 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
154
153
// In this case we assume this will be the next history entry from the
155
154
// last seen entry.
156
155
urlStrategy! .replaceState (
157
- _tagWithSerialCount (event.state, _lastSeenSerialCount + 1 ),
158
- 'flutter' ,
159
- currentPath);
156
+ _tagWithSerialCount (event.state, _lastSeenSerialCount + 1 ),
157
+ 'flutter' ,
158
+ currentPath);
160
159
}
161
160
_lastSeenSerialCount = _currentSerialCount;
162
161
if (window._onPlatformMessage != null ) {
163
162
window.invokeOnPlatformMessage (
164
163
'flutter/navigation' ,
165
164
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
+ })),
171
169
(_) {},
172
170
);
173
171
}
174
172
}
175
173
176
174
@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 ;
184
178
}
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 ();
189
181
190
- @override
191
- Future <void > tearDown () async {
192
182
// Restores the html browser history.
193
183
assert (_hasSerialCount (currentState));
194
184
int backCount = _currentSerialCount;
@@ -197,8 +187,10 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
197
187
}
198
188
// Unwrap state.
199
189
assert (_hasSerialCount (currentState) && _currentSerialCount == 0 );
190
+ final Map <dynamic , dynamic > stateMap =
191
+ currentState as Map <dynamic , dynamic >;
200
192
urlStrategy! .replaceState (
201
- currentState ['state' ],
193
+ stateMap ['state' ],
202
194
'flutter' ,
203
195
currentPath,
204
196
);
@@ -222,35 +214,60 @@ class MultiEntriesBrowserHistory extends BrowserHistory {
222
214
/// * [MultiEntriesBrowserHistory] , which is used when the framework uses a
223
215
/// Router for routing.
224
216
class 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
+
225
240
static const MethodCall _popRouteMethodCall = MethodCall ('popRoute' );
226
241
static const String _kFlutterTag = 'flutter' ;
227
242
static const String _kOriginTag = 'origin' ;
228
243
229
- Map <String , dynamic > _wrapOriginState (dynamic state) {
244
+ Map <String , dynamic > _wrapOriginState (Object ? state) {
230
245
return < String , dynamic > {_kOriginTag: true , 'state' : state};
231
246
}
232
- dynamic _unwrapOriginState (dynamic state) {
247
+
248
+ Object ? _unwrapOriginState (Object ? state) {
233
249
assert (_isOriginEntry (state));
234
250
final Map <dynamic , dynamic > originState = state as Map <dynamic , dynamic >;
235
251
return originState['state' ];
236
252
}
253
+
237
254
Map <String , bool > _flutterState = < String , bool > {_kFlutterTag: true };
238
255
239
256
/// The origin entry is the history entry that the Flutter app landed on. It's
240
257
/// created by the browser when the user navigates to the url of the app.
241
- bool _isOriginEntry (dynamic state) {
258
+ bool _isOriginEntry (Object ? state) {
242
259
return state is Map && state[_kOriginTag] == true ;
243
260
}
244
261
245
262
/// The flutter entry is a history entry that we maintain on top of the origin
246
263
/// entry. It allows us to catch popstate events when the user hits the back
247
264
/// button.
248
- bool _isFlutterEntry (dynamic state) {
265
+ bool _isFlutterEntry (Object ? state) {
249
266
return state is Map && state[_kFlutterTag] == true ;
250
267
}
251
268
252
269
@override
253
- void setRouteName (String ? routeName, {dynamic ? state}) {
270
+ void setRouteName (String ? routeName, {Object ? state}) {
254
271
if (urlStrategy != null ) {
255
272
_setupFlutterEntry (urlStrategy! , replace: true , path: routeName);
256
273
}
@@ -260,7 +277,7 @@ class SingleEntryBrowserHistory extends BrowserHistory {
260
277
@override
261
278
void onPopState (covariant html.PopStateEvent event) {
262
279
if (_isOriginEntry (event.state)) {
263
- _setupFlutterEntry (_urlStrategy ! );
280
+ _setupFlutterEntry (urlStrategy ! );
264
281
265
282
// 2. Send a 'popRoute' platform message so the app can handle it accordingly.
266
283
if (window._onPlatformMessage != null ) {
@@ -302,22 +319,22 @@ class SingleEntryBrowserHistory extends BrowserHistory {
302
319
// 2. Then we remove the new entry.
303
320
// This will take us back to our "flutter" entry and it causes a new
304
321
// popstate event that will be handled in the "else if" section above.
305
- _urlStrategy ! .go (- 1 );
322
+ urlStrategy ! .go (- 1 );
306
323
}
307
324
}
308
325
309
326
/// This method should be called when the Origin Entry is active. It just
310
327
/// replaces the state of the entry so that we can recognize it later using
311
328
/// [_isOriginEntry] inside [_popStateListener] .
312
- void _setupOriginEntry (JsUrlStrategy strategy) {
329
+ void _setupOriginEntry (UrlStrategy strategy) {
313
330
assert (strategy != null ); // ignore: unnecessary_null_comparison
314
331
strategy.replaceState (_wrapOriginState (currentState), 'origin' , '' );
315
332
}
316
333
317
334
/// This method is used manipulate the Flutter Entry which is always the
318
335
/// active entry while the Flutter app is running.
319
336
void _setupFlutterEntry (
320
- JsUrlStrategy strategy, {
337
+ UrlStrategy strategy, {
321
338
bool replace = false ,
322
339
String ? path,
323
340
}) {
@@ -330,28 +347,18 @@ class SingleEntryBrowserHistory extends BrowserHistory {
330
347
}
331
348
}
332
349
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
-
348
350
@override
349
351
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 ;
355
354
}
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);
356
363
}
357
364
}
0 commit comments