Unverified Commit 26398e68 authored by chunhtai's avatar chunhtai Committed by GitHub

Fix crashes when current route parsing transactions are discarded (#100657)

* Fix crashes when current route parsing transactions are discarded

* refactor

* update
parent d60272a2
......@@ -466,7 +466,7 @@ class Router<T> extends StatefulWidget {
}
typedef _AsyncPassthrough<Q> = Future<Q> Function(Q);
typedef _DelegateRouteSetter<T> = Future<void> Function(T);
typedef _RouteSetter<T> = Future<void> Function(T);
/// The [Router]'s intention when it reports a new [RouteInformation] to the
/// [RouteInformationProvider].
......@@ -492,8 +492,7 @@ enum RouteInformationReportingType {
}
class _RouterState<T> extends State<Router<T>> with RestorationMixin {
Object? _currentRouteInformationParserTransaction;
Object? _currentRouterDelegateTransaction;
Object? _currentRouterTransaction;
RouteInformationReportingType? _currentIntentionToReport;
final _RestorableRouteInformation _routeInformation = _RestorableRouteInformation();
......@@ -593,8 +592,7 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
widget.backButtonDispatcher != oldWidget.backButtonDispatcher ||
widget.routeInformationParser != oldWidget.routeInformationParser ||
widget.routerDelegate != oldWidget.routerDelegate) {
_currentRouteInformationParserTransaction = Object();
_currentRouterDelegateTransaction = Object();
_currentRouterTransaction = Object();
}
if (widget.routeInformationProvider != oldWidget.routeInformationProvider) {
oldWidget.routeInformationProvider?.removeListener(_handleRouteInformationProviderNotification);
......@@ -619,20 +617,27 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
widget.routeInformationProvider?.removeListener(_handleRouteInformationProviderNotification);
widget.backButtonDispatcher?.removeCallback(_handleBackButtonDispatcherNotification);
widget.routerDelegate.removeListener(_handleRouterDelegateNotification);
_currentRouteInformationParserTransaction = null;
_currentRouterDelegateTransaction = null;
_currentRouterTransaction = null;
super.dispose();
}
void _processRouteInformation(RouteInformation information, ValueGetter<_DelegateRouteSetter<T>> delegateRouteSetter) {
_currentRouteInformationParserTransaction = Object();
_currentRouterDelegateTransaction = Object();
void _processRouteInformation(RouteInformation information, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
_currentRouterTransaction = Object();
widget.routeInformationParser!
.parseRouteInformation(information)
.then<T>(_verifyRouteInformationParserStillCurrent(_currentRouteInformationParserTransaction, widget))
.then<void>(delegateRouteSetter())
.then<void>(_verifyRouterDelegatePushStillCurrent(_currentRouterDelegateTransaction, widget))
.then<void>(_rebuild);
.then<void>(_processParsedRouteInformation(_currentRouterTransaction, delegateRouteSetter));
}
_RouteSetter<T> _processParsedRouteInformation(Object? transaction, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
return (T data) async {
if (_currentRouterTransaction != transaction) {
return;
}
await delegateRouteSetter()(data);
if (_currentRouterTransaction == transaction) {
_rebuild();
}
};
}
void _handleRouteInformationProviderNotification() {
......@@ -641,56 +646,21 @@ class _RouterState<T> extends State<Router<T>> with RestorationMixin {
}
Future<bool> _handleBackButtonDispatcherNotification() {
_currentRouteInformationParserTransaction = Object();
_currentRouterDelegateTransaction = Object();
_currentRouterTransaction = Object();
return widget.routerDelegate
.popRoute()
.then<bool>(_verifyRouterDelegatePopStillCurrent(_currentRouterDelegateTransaction, widget))
.then<bool>((bool data) {
_rebuild();
return SynchronousFuture<bool>(data);
});
}
static final Future<dynamic> _never = Completer<dynamic>().future; // won't ever complete
_AsyncPassthrough<T> _verifyRouteInformationParserStillCurrent(Object? transaction, Router<T> originalWidget) {
return (T data) {
if (transaction == _currentRouteInformationParserTransaction &&
widget.routeInformationProvider == originalWidget.routeInformationProvider &&
widget.backButtonDispatcher == originalWidget.backButtonDispatcher &&
widget.routeInformationParser == originalWidget.routeInformationParser &&
widget.routerDelegate == originalWidget.routerDelegate) {
return SynchronousFuture<T>(data);
}
return _never as Future<T>;
};
}
_AsyncPassthrough<void> _verifyRouterDelegatePushStillCurrent(Object? transaction, Router<T> originalWidget) {
return (void data) {
if (transaction == _currentRouterDelegateTransaction &&
widget.routeInformationProvider == originalWidget.routeInformationProvider &&
widget.backButtonDispatcher == originalWidget.backButtonDispatcher &&
widget.routeInformationParser == originalWidget.routeInformationParser &&
widget.routerDelegate == originalWidget.routerDelegate)
return SynchronousFuture<void>(data);
return _never;
};
.then<bool>(_handleRoutePopped(_currentRouterTransaction));
}
_AsyncPassthrough<bool> _verifyRouterDelegatePopStillCurrent(Object? transaction, Router<T> originalWidget) {
_AsyncPassthrough<bool> _handleRoutePopped(Object? transaction) {
return (bool data) {
if (transaction == _currentRouterDelegateTransaction &&
widget.routeInformationProvider == originalWidget.routeInformationProvider &&
widget.backButtonDispatcher == originalWidget.backButtonDispatcher &&
widget.routeInformationParser == originalWidget.routeInformationParser &&
widget.routerDelegate == originalWidget.routerDelegate) {
return SynchronousFuture<bool>(data);
if (transaction != _currentRouterTransaction) {
// A rebuilt was trigger from a different source. Returns true to
// prevent bubbling.
return SynchronousFuture<bool>(true);
}
// A rebuilt was trigger from a different source. Returns true to
// prevent bubbling.
return SynchronousFuture<bool>(true);
_rebuild();
return SynchronousFuture<bool>(data);
};
}
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
......@@ -77,6 +79,54 @@ void main() {
});
});
testWidgets('Interrupts route parsing should not crash', (WidgetTester tester) async {
final SimpleRouteInformationProvider provider = SimpleRouteInformationProvider();
provider.value = const RouteInformation(
location: 'initial',
);
final CompleterRouteInformationParser parser = CompleterRouteInformationParser();
final SimpleAsyncRouterDelegate delegate = SimpleAsyncRouterDelegate(
builder: (BuildContext context, RouteInformation? information) {
if (information == null)
return const Text('waiting');
return Text(information.location!);
},
);
await tester.runAsync(() async {
await tester.pumpWidget(buildBoilerPlate(
Router<RouteInformation>(
routeInformationProvider: provider,
routeInformationParser: parser,
routerDelegate: delegate,
),
));
// Future has not yet completed.
expect(find.text('waiting'), findsOneWidget);
final Completer<void> firstTransactionCompleter = parser.completer;
// Start a new parsing transaction before the previous one complete.
provider.value = const RouteInformation(
location: 'update',
);
await tester.pump();
expect(find.text('waiting'), findsOneWidget);
// Completing the previous transaction does not cause an update.
firstTransactionCompleter.complete();
await firstTransactionCompleter.future;
await tester.pump();
expect(find.text('waiting'), findsOneWidget);
expect(tester.takeException(), isNull);
// Make sure the new transaction can complete and update correctly.
parser.completer.complete();
await parser.completer.future;
await delegate.setNewRouteFuture;
await tester.pump();
expect(find.text('update'), findsOneWidget);
});
});
testWidgets('Router.maybeOf can be null', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
await tester.pumpWidget(buildBoilerPlate(
......@@ -1414,6 +1464,24 @@ class SimpleAsyncRouteInformationParser extends RouteInformationParser<RouteInfo
}
}
class CompleterRouteInformationParser extends RouteInformationParser<RouteInformation> {
CompleterRouteInformationParser();
late Completer<void> completer;
@override
Future<RouteInformation> parseRouteInformation(RouteInformation information) async {
completer = Completer<void>();
await completer.future;
return SynchronousFuture<RouteInformation>(information);
}
@override
RouteInformation restoreRouteInformation(RouteInformation configuration) {
return configuration;
}
}
class SimpleAsyncRouterDelegate extends RouterDelegate<RouteInformation> with ChangeNotifier {
SimpleAsyncRouterDelegate({
required this.builder,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment