Skip to content

Commit

Permalink
[web] Don't reset history on hot restart (flutter#27872)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdebbar authored Aug 13, 2021
1 parent 872b0de commit 5e515af
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 23 deletions.
18 changes: 16 additions & 2 deletions lib/web_ui/lib/src/engine/navigation/history.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ import '../services/message_codec.dart';
import '../services/message_codecs.dart';
import 'url_strategy.dart';

/// Infers the history mode from the existing browser history state, then
/// creates the appropriate instance of [BrowserHistory] for it.
///
/// If it can't infer, it creates a [MultiEntriesBrowserHistory] by default.
BrowserHistory createHistoryForExistingState(UrlStrategy? urlStrategy) {
if (urlStrategy != null) {
final Object? state = urlStrategy.getState();
if (SingleEntryBrowserHistory._isOriginEntry(state) || SingleEntryBrowserHistory._isFlutterEntry(state)) {
return SingleEntryBrowserHistory(urlStrategy: urlStrategy);
}
}
return MultiEntriesBrowserHistory(urlStrategy: urlStrategy);
}

/// An abstract class that provides the API for [EngineWindow] to delegate its
/// navigating events.
///
Expand Down Expand Up @@ -263,14 +277,14 @@ class SingleEntryBrowserHistory extends BrowserHistory {

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

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

Expand Down
55 changes: 34 additions & 21 deletions lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import 'package:js/js.dart';
import 'package:meta/meta.dart';
import 'package:ui/ui.dart' as ui;

import '../engine.dart' show registerHotRestartListener;
import 'browser_detection.dart';
import 'navigation/history.dart';
import 'navigation/js_url_strategy.dart';
Expand Down Expand Up @@ -50,12 +49,8 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
engineDispatcher.windows[_windowId] = this;
engineDispatcher.windowConfigurations[_windowId] = const ui.ViewConfiguration();
if (_isUrlStrategySet) {
_browserHistory =
MultiEntriesBrowserHistory(urlStrategy: _customUrlStrategy);
_browserHistory = createHistoryForExistingState(_customUrlStrategy);
}
registerHotRestartListener(() {
window.resetHistory();
});
}

final Object _windowId;
Expand All @@ -67,7 +62,7 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow {
/// button, etc.
BrowserHistory get browserHistory {
return _browserHistory ??=
MultiEntriesBrowserHistory(urlStrategy: _urlStrategyForInitialization);
createHistoryForExistingState(_urlStrategyForInitialization);
}

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

Future<void> _useSingleEntryBrowserHistory() async {
// Recreate the browser history mode that's appropriate for the existing
// history state.
//
// If it happens to be a single-entry one, then there's nothing further to do.
//
// But if it's a multi-entry one, it will be torn down below and replaced
// with a single-entry history.
//
// See: https://github.com/flutter/flutter/issues/79241
_browserHistory ??=
createHistoryForExistingState(_urlStrategyForInitialization);

if (_browserHistory is SingleEntryBrowserHistory) {
return;
}

final UrlStrategy? strategy;
if (_browserHistory == null) {
strategy = _urlStrategyForInitialization;
} else {
strategy = _browserHistory?.urlStrategy;
await _browserHistory?.tearDown();
}
// At this point, we know that `_browserHistory` is a non-null
// `MultiEntriesBrowserHistory` instance.
final UrlStrategy? strategy = _browserHistory?.urlStrategy;
await _browserHistory?.tearDown();
_browserHistory = SingleEntryBrowserHistory(urlStrategy: strategy);
}

Future<void> _useMultiEntryBrowserHistory() async {
// Recreate the browser history mode that's appropriate for the existing
// history state.
//
// If it happens to be a multi-entry one, then there's nothing further to do.
//
// But if it's a single-entry one, it will be torn down below and replaced
// with a multi-entry history.
//
// See: https://github.com/flutter/flutter/issues/79241
_browserHistory ??=
createHistoryForExistingState(_urlStrategyForInitialization);

if (_browserHistory is MultiEntriesBrowserHistory) {
return;
}

final UrlStrategy? strategy;
if (_browserHistory == null) {
strategy = _urlStrategyForInitialization;
} else {
strategy = _browserHistory?.urlStrategy;
await _browserHistory?.tearDown();
}
// At this point, we know that `_browserHistory` is a non-null
// `SingleEntryBrowserHistory` instance.
final UrlStrategy? strategy = _browserHistory?.urlStrategy;
await _browserHistory?.tearDown();
_browserHistory = MultiEntriesBrowserHistory(urlStrategy: strategy);
}

Expand Down
44 changes: 44 additions & 0 deletions lib/web_ui/test/engine/history_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,50 @@ void main() {
}

void testMain() {
test('createHistoryForExistingState', () {
TestUrlStrategy strategy;
BrowserHistory history;

// No url strategy.
history = createHistoryForExistingState(null);
expect(history, isA<MultiEntriesBrowserHistory>());
expect(history.urlStrategy, isNull);

// Random history state.
strategy = TestUrlStrategy.fromEntry(
const TestHistoryEntry(<dynamic, dynamic>{'foo': 123}, null, '/'),
);
history = createHistoryForExistingState(strategy);
expect(history, isA<MultiEntriesBrowserHistory>());
expect(history.urlStrategy, strategy);

// Multi-entry history state.
final Map<dynamic, dynamic> state = <dynamic, dynamic>{
'serialCount': 1,
'state': <dynamic, dynamic>{'foo': 123},
};
strategy = TestUrlStrategy.fromEntry(TestHistoryEntry(state, null, '/'));
history = createHistoryForExistingState(strategy);
expect(history, isA<MultiEntriesBrowserHistory>());
expect(history.urlStrategy, strategy);

// Single-entry history "origin" state.
strategy = TestUrlStrategy.fromEntry(
const TestHistoryEntry(<dynamic, dynamic>{'origin': true}, null, '/'),
);
history = createHistoryForExistingState(strategy);
expect(history, isA<SingleEntryBrowserHistory>());
expect(history.urlStrategy, strategy);

// Single-entry history "flutter" state.
strategy = TestUrlStrategy.fromEntry(
const TestHistoryEntry(<dynamic, dynamic>{'flutter': true}, null, '/'),
);
history = createHistoryForExistingState(strategy);
expect(history, isA<SingleEntryBrowserHistory>());
expect(history.urlStrategy, strategy);
});

group('$SingleEntryBrowserHistory', () {
final PlatformMessagesSpy spy = PlatformMessagesSpy();

Expand Down

0 comments on commit 5e515af

Please sign in to comment.