Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit fbeb8bc

Browse files
committed
refactor
1 parent 5f04364 commit fbeb8bc

File tree

8 files changed

+378
-301
lines changed

8 files changed

+378
-301
lines changed

lib/web_ui/lib/src/engine/history/history.dart

Lines changed: 102 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
2424
abstract 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.
11594
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+
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.
224216
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+
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

Comments
 (0)