Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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