Skip to content

Commit f83d8cf

Browse files
authored
[web] Popping a nameless route should preserve the correct route name (flutter#173652)
Fixes flutter#173356
1 parent 72e1bf1 commit f83d8cf

File tree

3 files changed

+57
-25
lines changed

3 files changed

+57
-25
lines changed

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

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'package:meta/meta.dart';
66
import 'package:ui/ui.dart' as ui;
77
import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
88

9-
import '../dom.dart';
109
import '../platform_dispatcher.dart';
1110
import '../services/message_codec.dart';
1211
import '../services/message_codecs.dart';
@@ -257,20 +256,30 @@ class SingleEntryBrowserHistory extends BrowserHistory {
257256

258257
_setupStrategy(strategy);
259258

260-
final String path = currentPath;
261-
if (!_isFlutterEntry(domWindow.history.state)) {
259+
_currentRouteName = currentPath;
260+
if (!_isFlutterEntry(currentState)) {
262261
// An entry may not have come from Flutter, for example, when the user
263262
// refreshes the page. They land directly on the "flutter" entry, so
264263
// there's no need to set up the "origin" and "flutter" entries, we can
265264
// safely assume they are already set up.
266265
_setupOriginEntry(strategy);
267-
_setupFlutterEntry(strategy, path: path);
266+
_setupFlutterEntry(strategy);
268267
}
269268
}
270269

271270
@override
272271
final ui_web.UrlStrategy? urlStrategy;
273272

273+
/// The route name of the current page.
274+
///
275+
/// This is updated whenever the framework calls `setRouteName`. This is then
276+
/// used when the user hits the back button to pop a nameless route, to restore
277+
/// the route name from before the nameless route was pushed.
278+
///
279+
/// This is also used to track the user-provided url when they change it
280+
/// directly in the address bar.
281+
String _currentRouteName = '/';
282+
274283
static const MethodCall _popRouteMethodCall = MethodCall('popRoute');
275284
static const String _kFlutterTag = 'flutter';
276285
static const String _kOriginTag = 'origin';
@@ -303,11 +312,11 @@ class SingleEntryBrowserHistory extends BrowserHistory {
303312
@override
304313
void setRouteName(String? routeName, {Object? state, bool replace = false}) {
305314
if (urlStrategy != null) {
306-
_setupFlutterEntry(urlStrategy!, replace: true, path: routeName);
315+
_currentRouteName = routeName ?? currentPath;
316+
_setupFlutterEntry(urlStrategy!, replace: true);
307317
}
308318
}
309319

310-
String? _userProvidedRouteName;
311320
@override
312321
void onPopState(Object? state) {
313322
if (_isOriginEntry(state)) {
@@ -323,17 +332,13 @@ class SingleEntryBrowserHistory extends BrowserHistory {
323332
// We get into this scenario when the user changes the url manually. It
324333
// causes a new entry to be pushed on top of our "flutter" one. When this
325334
// happens it first goes to the "else" section below where we capture the
326-
// path into `_userProvidedRouteName` then trigger a history back which
335+
// path into `_currentRouteName` then trigger a history back which
327336
// brings us here.
328-
assert(_userProvidedRouteName != null);
329-
330-
final String newRouteName = _userProvidedRouteName!;
331-
_userProvidedRouteName = null;
332337

333338
// Send a 'pushRoute' platform message so the app handles it accordingly.
334339
EnginePlatformDispatcher.instance.invokeOnPlatformMessage(
335340
'flutter/navigation',
336-
const JSONMethodCodec().encodeMethodCall(MethodCall('pushRoute', newRouteName)),
341+
const JSONMethodCodec().encodeMethodCall(MethodCall('pushRoute', _currentRouteName)),
337342
(_) {},
338343
);
339344
} else {
@@ -342,7 +347,7 @@ class SingleEntryBrowserHistory extends BrowserHistory {
342347
// example.
343348

344349
// 1. We first capture the user's desired path.
345-
_userProvidedRouteName = currentPath;
350+
_currentRouteName = currentPath;
346351

347352
// 2. Then we remove the new entry.
348353
// This will take us back to our "flutter" entry and it causes a new
@@ -360,13 +365,9 @@ class SingleEntryBrowserHistory extends BrowserHistory {
360365

361366
/// This method is used manipulate the Flutter Entry which is always the
362367
/// active entry while the Flutter app is running.
363-
void _setupFlutterEntry(ui_web.UrlStrategy strategy, {bool replace = false, String? path}) {
364-
path ??= currentPath;
365-
if (replace) {
366-
strategy.replaceState(_flutterState, 'flutter', path);
367-
} else {
368-
strategy.pushState(_flutterState, 'flutter', path);
369-
}
368+
void _setupFlutterEntry(ui_web.UrlStrategy strategy, {bool replace = false}) {
369+
final updateState = replace ? strategy.replaceState : strategy.pushState;
370+
updateState(_flutterState, 'flutter', _currentRouteName);
370371
}
371372

372373
@override

engine/src/flutter/lib/web_ui/lib/src/engine/test_embedding.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const bool _debugLogHistoryActions = false;
1515
class TestHistoryEntry {
1616
const TestHistoryEntry(this.state, this.title, this.url);
1717

18-
final dynamic state;
18+
final Object? state;
1919
final String? title;
2020
final String url;
2121

@@ -45,7 +45,7 @@ class TestUrlStrategy implements ui_web.UrlStrategy {
4545
String getPath() => currentEntry.url;
4646

4747
@override
48-
dynamic getState() => currentEntry.state;
48+
Object? getState() => currentEntry.state;
4949

5050
int _currentEntryIndex;
5151
int get currentEntryIndex => _currentEntryIndex;

engine/src/flutter/lib/web_ui/test/engine/history_test.dart

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ void testMain() {
203203
expect(spy.messages[0].channel, 'flutter/navigation');
204204
expect(spy.messages[0].methodName, 'popRoute');
205205
expect(spy.messages[0].methodArguments, isNull);
206+
// The framework responds by updating to the most current route name.
207+
await routeUpdated('/home');
206208
// We still have 2 entries.
207209
expect(strategy.history, hasLength(2));
208210
expect(strategy.currentEntryIndex, 1);
@@ -293,7 +295,7 @@ void testMain() {
293295
expect(spy.messages[0].methodName, 'pushRoute');
294296
expect(spy.messages[0].methodArguments, '/page3');
295297
spy.messages.clear();
296-
// 2. The framework sends a `routePushed` platform message.
298+
// 2. The framework sends a `routeUpdated` platform message.
297299
await routeUpdated('/page3');
298300
// 3. The history state should reflect that /page3 is currently active.
299301
expect(strategy.history, hasLength(3));
@@ -309,9 +311,9 @@ void testMain() {
309311
expect(spy.messages[0].methodName, 'popRoute');
310312
expect(spy.messages[0].methodArguments, isNull);
311313
spy.messages.clear();
312-
// 2. The framework sends a `routePopped` platform message.
314+
// 2. The framework sends a `routeUpdated` platform message.
313315
await routeUpdated('/home');
314-
// 3. The history state should reflect that /page1 is currently active.
316+
// 3. The history state should reflect that /home is currently active.
315317
expect(strategy.history, hasLength(2));
316318
expect(strategy.currentEntryIndex, 1);
317319
expect(strategy.currentEntry.state, flutterState);
@@ -341,6 +343,35 @@ void testMain() {
341343
expect(strategy.currentEntry.state, flutterState);
342344
expect(strategy.currentEntry.url, '/home');
343345
});
346+
347+
test('popping a nameless route does not change url', () async {
348+
final TestUrlStrategy strategy = TestUrlStrategy.fromEntry(
349+
const TestHistoryEntry(null, null, '/home'),
350+
);
351+
await implicitView.debugInitializeHistory(strategy, useSingle: true);
352+
353+
// Go to a named route.
354+
await routeUpdated('/named-route');
355+
expect(strategy.currentEntry.url, '/named-route');
356+
357+
// Now, push a nameless route. The url shouldn't change.
358+
// In a real app, this would be `Navigator.push(context, ...)`;
359+
// Here, we simulate it by NOT calling `routeUpdated`.
360+
361+
// Click back to pop the nameless route.
362+
await strategy.go(-1);
363+
364+
// A `popRoute` message should have been sent to the framework.
365+
expect(spy.messages, hasLength(1));
366+
expect(spy.messages[0].channel, 'flutter/navigation');
367+
expect(spy.messages[0].methodName, 'popRoute');
368+
369+
// Because the popped route was nameless, the framework doesn't send any updated route
370+
// information.
371+
372+
// The url from before the nameless route should've been preserved.
373+
expect(strategy.currentEntry.url, '/named-route');
374+
});
344375
});
345376

346377
group('$MultiEntriesBrowserHistory', () {

0 commit comments

Comments
 (0)