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

Commit 5e515af

Browse files
authored
[web] Don't reset history on hot restart (#27872)
1 parent 872b0de commit 5e515af

File tree

3 files changed

+94
-23
lines changed

3 files changed

+94
-23
lines changed

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ import '../services/message_codec.dart';
1111
import '../services/message_codecs.dart';
1212
import 'url_strategy.dart';
1313

14+
/// Infers the history mode from the existing browser history state, then
15+
/// creates the appropriate instance of [BrowserHistory] for it.
16+
///
17+
/// If it can't infer, it creates a [MultiEntriesBrowserHistory] by default.
18+
BrowserHistory createHistoryForExistingState(UrlStrategy? urlStrategy) {
19+
if (urlStrategy != null) {
20+
final Object? state = urlStrategy.getState();
21+
if (SingleEntryBrowserHistory._isOriginEntry(state) || SingleEntryBrowserHistory._isFlutterEntry(state)) {
22+
return SingleEntryBrowserHistory(urlStrategy: urlStrategy);
23+
}
24+
}
25+
return MultiEntriesBrowserHistory(urlStrategy: urlStrategy);
26+
}
27+
1428
/// An abstract class that provides the API for [EngineWindow] to delegate its
1529
/// navigating events.
1630
///
@@ -263,14 +277,14 @@ class SingleEntryBrowserHistory extends BrowserHistory {
263277

264278
/// The origin entry is the history entry that the Flutter app landed on. It's
265279
/// created by the browser when the user navigates to the url of the app.
266-
bool _isOriginEntry(Object? state) {
280+
static bool _isOriginEntry(Object? state) {
267281
return state is Map && state[_kOriginTag] == true;
268282
}
269283

270284
/// The flutter entry is a history entry that we maintain on top of the origin
271285
/// entry. It allows us to catch popstate events when the user hits the back
272286
/// button.
273-
bool _isFlutterEntry(Object? state) {
287+
static bool _isFlutterEntry(Object? state) {
274288
return state is Map && state[_kFlutterTag] == true;
275289
}
276290

lib/web_ui/lib/src/engine/window.dart

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import 'package:js/js.dart';
1313
import 'package:meta/meta.dart';
1414
import 'package:ui/ui.dart' as ui;
1515

16-
import '../engine.dart' show registerHotRestartListener;
1716
import 'browser_detection.dart';
1817
import 'navigation/history.dart';
1918
import 'navigation/js_url_strategy.dart';
@@ -50,12 +49,8 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
5049
engineDispatcher.windows[_windowId] = this;
5150
engineDispatcher.windowConfigurations[_windowId] = const ui.ViewConfiguration();
5251
if (_isUrlStrategySet) {
53-
_browserHistory =
54-
MultiEntriesBrowserHistory(urlStrategy: _customUrlStrategy);
52+
_browserHistory = createHistoryForExistingState(_customUrlStrategy);
5553
}
56-
registerHotRestartListener(() {
57-
window.resetHistory();
58-
});
5954
}
6055

6156
final Object _windowId;
@@ -67,7 +62,7 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
6762
/// button, etc.
6863
BrowserHistory get browserHistory {
6964
return _browserHistory ??=
70-
MultiEntriesBrowserHistory(urlStrategy: _urlStrategyForInitialization);
65+
createHistoryForExistingState(_urlStrategyForInitialization);
7166
}
7267

7368
UrlStrategy? get _urlStrategyForInitialization {
@@ -82,32 +77,50 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
8277
_browserHistory; // Must be either SingleEntryBrowserHistory or MultiEntriesBrowserHistory.
8378

8479
Future<void> _useSingleEntryBrowserHistory() async {
80+
// Recreate the browser history mode that's appropriate for the existing
81+
// history state.
82+
//
83+
// If it happens to be a single-entry one, then there's nothing further to do.
84+
//
85+
// But if it's a multi-entry one, it will be torn down below and replaced
86+
// with a single-entry history.
87+
//
88+
// See: https://github.com/flutter/flutter/issues/79241
89+
_browserHistory ??=
90+
createHistoryForExistingState(_urlStrategyForInitialization);
91+
8592
if (_browserHistory is SingleEntryBrowserHistory) {
8693
return;
8794
}
8895

89-
final UrlStrategy? strategy;
90-
if (_browserHistory == null) {
91-
strategy = _urlStrategyForInitialization;
92-
} else {
93-
strategy = _browserHistory?.urlStrategy;
94-
await _browserHistory?.tearDown();
95-
}
96+
// At this point, we know that `_browserHistory` is a non-null
97+
// `MultiEntriesBrowserHistory` instance.
98+
final UrlStrategy? strategy = _browserHistory?.urlStrategy;
99+
await _browserHistory?.tearDown();
96100
_browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy);
97101
}
98102

99103
Future<void> _useMultiEntryBrowserHistory() async {
104+
// Recreate the browser history mode that's appropriate for the existing
105+
// history state.
106+
//
107+
// If it happens to be a multi-entry one, then there's nothing further to do.
108+
//
109+
// But if it's a single-entry one, it will be torn down below and replaced
110+
// with a multi-entry history.
111+
//
112+
// See: https://github.com/flutter/flutter/issues/79241
113+
_browserHistory ??=
114+
createHistoryForExistingState(_urlStrategyForInitialization);
115+
100116
if (_browserHistory is MultiEntriesBrowserHistory) {
101117
return;
102118
}
103119

104-
final UrlStrategy? strategy;
105-
if (_browserHistory == null) {
106-
strategy = _urlStrategyForInitialization;
107-
} else {
108-
strategy = _browserHistory?.urlStrategy;
109-
await _browserHistory?.tearDown();
110-
}
120+
// At this point, we know that `_browserHistory` is a non-null
121+
// `SingleEntryBrowserHistory` instance.
122+
final UrlStrategy? strategy = _browserHistory?.urlStrategy;
123+
await _browserHistory?.tearDown();
111124
_browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy);
112125
}
113126

lib/web_ui/test/engine/history_test.dart

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,50 @@ void main() {
3939
}
4040

4141
void testMain() {
42+
test('createHistoryForExistingState', () {
43+
TestUrlStrategy strategy;
44+
BrowserHistory history;
45+
46+
// No url strategy.
47+
history = createHistoryForExistingState(null);
48+
expect(history, isA<MultiEntriesBrowserHistory>());
49+
expect(history.urlStrategy, isNull);
50+
51+
// Random history state.
52+
strategy = TestUrlStrategy.fromEntry(
53+
const TestHistoryEntry(<dynamic, dynamic>{'foo': 123}, null, '/'),
54+
);
55+
history = createHistoryForExistingState(strategy);
56+
expect(history, isA<MultiEntriesBrowserHistory>());
57+
expect(history.urlStrategy, strategy);
58+
59+
// Multi-entry history state.
60+
final Map<dynamic, dynamic> state = <dynamic, dynamic>{
61+
'serialCount': 1,
62+
'state': <dynamic, dynamic>{'foo': 123},
63+
};
64+
strategy = TestUrlStrategy.fromEntry(TestHistoryEntry(state, null, '/'));
65+
history = createHistoryForExistingState(strategy);
66+
expect(history, isA<MultiEntriesBrowserHistory>());
67+
expect(history.urlStrategy, strategy);
68+
69+
// Single-entry history "origin" state.
70+
strategy = TestUrlStrategy.fromEntry(
71+
const TestHistoryEntry(<dynamic, dynamic>{'origin': true}, null, '/'),
72+
);
73+
history = createHistoryForExistingState(strategy);
74+
expect(history, isA<SingleEntryBrowserHistory>());
75+
expect(history.urlStrategy, strategy);
76+
77+
// Single-entry history "flutter" state.
78+
strategy = TestUrlStrategy.fromEntry(
79+
const TestHistoryEntry(<dynamic, dynamic>{'flutter': true}, null, '/'),
80+
);
81+
history = createHistoryForExistingState(strategy);
82+
expect(history, isA<SingleEntryBrowserHistory>());
83+
expect(history.urlStrategy, strategy);
84+
});
85+
4286
group('$SingleEntryBrowserHistory', () {
4387
final PlatformMessagesSpy spy = PlatformMessagesSpy();
4488

0 commit comments

Comments
 (0)