Disallow unsafe ancestor methods (#9425)
This commit is contained in:
parent
acd75c0a3b
commit
b52d659406
@ -1585,6 +1585,11 @@ abstract class BuildContext {
|
|||||||
/// (directly or indirectly) from build methods, layout and paint callbacks, or
|
/// (directly or indirectly) from build methods, layout and paint callbacks, or
|
||||||
/// from [State.didChangeDependencies].
|
/// from [State.didChangeDependencies].
|
||||||
///
|
///
|
||||||
|
/// This method should not be called from [State.deactivate] or [State.dispose]
|
||||||
|
/// because the element tree is no longer stable at that time. To refer to
|
||||||
|
/// an ancestor from one of those methods, save a reference to the ancestor
|
||||||
|
/// in [State.didChangeDependencies].
|
||||||
|
///
|
||||||
/// It is also possible to call this from interaction event handlers (e.g.
|
/// It is also possible to call this from interaction event handlers (e.g.
|
||||||
/// gesture callbacks) or timers, to obtain a value once, if that value is not
|
/// gesture callbacks) or timers, to obtain a value once, if that value is not
|
||||||
/// going to be cached and reused later.
|
/// going to be cached and reused later.
|
||||||
@ -1609,6 +1614,11 @@ abstract class BuildContext {
|
|||||||
/// widgets to obtain their corresponding [InheritedElement] object so that they
|
/// widgets to obtain their corresponding [InheritedElement] object so that they
|
||||||
/// can call [InheritedElement.dispatchDidChangeDependencies] to actually
|
/// can call [InheritedElement.dispatchDidChangeDependencies] to actually
|
||||||
/// notify the widgets that _did_ register such a relationship.
|
/// notify the widgets that _did_ register such a relationship.
|
||||||
|
///
|
||||||
|
/// This method should not be called from [State.deactivate] or [State.dispose]
|
||||||
|
/// because the element tree is no longer stable at that time. To refer to
|
||||||
|
/// an ancestor from one of those methods, save a reference to the ancestor
|
||||||
|
/// by calling [inheritFromWidgetOfExactType] in [State.didChangeDependencies].
|
||||||
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType);
|
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType);
|
||||||
|
|
||||||
/// Returns the nearest ancestor widget of the given type, which must be the
|
/// Returns the nearest ancestor widget of the given type, which must be the
|
||||||
@ -1623,6 +1633,11 @@ abstract class BuildContext {
|
|||||||
/// Calling this method is relatively expensive (O(N) in the depth of the
|
/// Calling this method is relatively expensive (O(N) in the depth of the
|
||||||
/// tree). Only call this method if the distance from this widget to the
|
/// tree). Only call this method if the distance from this widget to the
|
||||||
/// desired ancestor is known to be small and bounded.
|
/// desired ancestor is known to be small and bounded.
|
||||||
|
///
|
||||||
|
/// This method should not be called from [State.deactivate] or [State.dispose]
|
||||||
|
/// because the widget tree is no longer stable at that time. To refer to
|
||||||
|
/// an ancestor from one of those methods, save a reference to the ancestor
|
||||||
|
/// by calling [inheritFromWidgetOfExactType] in [State.didChangeDependencies].
|
||||||
Widget ancestorWidgetOfExactType(Type targetType);
|
Widget ancestorWidgetOfExactType(Type targetType);
|
||||||
|
|
||||||
/// Returns the [State] object of the nearest ancestor [StatefulWidget] widget
|
/// Returns the [State] object of the nearest ancestor [StatefulWidget] widget
|
||||||
@ -1631,7 +1646,7 @@ abstract class BuildContext {
|
|||||||
/// This should not be used from build methods, because the build context will
|
/// This should not be used from build methods, because the build context will
|
||||||
/// not be rebuilt if the value that would be returned by this method changes.
|
/// not be rebuilt if the value that would be returned by this method changes.
|
||||||
/// In general, [inheritFromWidgetOfExactType] is more appropriate for such
|
/// In general, [inheritFromWidgetOfExactType] is more appropriate for such
|
||||||
/// cases. This method is useful to change the state of an ancestor widget in
|
/// cases. This method is useful for changing the state of an ancestor widget in
|
||||||
/// a one-off manner, for example, to cause an ancestor scrolling list to
|
/// a one-off manner, for example, to cause an ancestor scrolling list to
|
||||||
/// scroll this build context's widget into view, or to move the focus in
|
/// scroll this build context's widget into view, or to move the focus in
|
||||||
/// response to user interaction.
|
/// response to user interaction.
|
||||||
@ -1645,6 +1660,11 @@ abstract class BuildContext {
|
|||||||
/// tree). Only call this method if the distance from this widget to the
|
/// tree). Only call this method if the distance from this widget to the
|
||||||
/// desired ancestor is known to be small and bounded.
|
/// desired ancestor is known to be small and bounded.
|
||||||
///
|
///
|
||||||
|
/// This method should not be called from [State.deactivate] or [State.dispose]
|
||||||
|
/// because the widget tree is no longer stable at that time. To refer to
|
||||||
|
/// an ancestor from one of those methods, save a reference to the ancestor
|
||||||
|
/// by calling [inheritFromWidgetOfExactType] in [State.didChangeDependencies].
|
||||||
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
///
|
///
|
||||||
/// ```dart
|
/// ```dart
|
||||||
@ -1663,6 +1683,11 @@ abstract class BuildContext {
|
|||||||
/// it is used by [Material] so that [InkWell] widgets can trigger the ink
|
/// it is used by [Material] so that [InkWell] widgets can trigger the ink
|
||||||
/// splash on the [Material]'s actual render object.
|
/// splash on the [Material]'s actual render object.
|
||||||
///
|
///
|
||||||
|
/// This method should not be called from [State.deactivate] or [State.dispose]
|
||||||
|
/// because the widget tree is no longer stable at that time. To refer to
|
||||||
|
/// an ancestor from one of those methods, save a reference to the ancestor
|
||||||
|
/// by calling [inheritFromWidgetOfExactType] in [State.didChangeDependencies].
|
||||||
|
///
|
||||||
/// Calling this method is relatively expensive (O(N) in the depth of the
|
/// Calling this method is relatively expensive (O(N) in the depth of the
|
||||||
/// tree). Only call this method if the distance from this widget to the
|
/// tree). Only call this method if the distance from this widget to the
|
||||||
/// desired ancestor is known to be small and bounded.
|
/// desired ancestor is known to be small and bounded.
|
||||||
@ -1677,6 +1702,11 @@ abstract class BuildContext {
|
|||||||
/// This is useful for inspecting the widget tree.
|
/// This is useful for inspecting the widget tree.
|
||||||
///
|
///
|
||||||
/// Calling this method is relatively expensive (O(N) in the depth of the tree).
|
/// Calling this method is relatively expensive (O(N) in the depth of the tree).
|
||||||
|
///
|
||||||
|
/// This method should not be called from [State.deactivate] or [State.dispose]
|
||||||
|
/// because the element tree is no longer stable at that time. To refer to
|
||||||
|
/// an ancestor from one of those methods, save a reference to the ancestor
|
||||||
|
/// by calling [inheritFromWidgetOfExactType] in [State.didChangeDependencies].
|
||||||
void visitAncestorElements(bool visitor(Element element));
|
void visitAncestorElements(bool visitor(Element element));
|
||||||
|
|
||||||
/// Walks the children of this widget.
|
/// Walks the children of this widget.
|
||||||
@ -2739,7 +2769,7 @@ abstract class Element implements BuildContext {
|
|||||||
'The size of this render object has not yet been determined because '
|
'The size of this render object has not yet been determined because '
|
||||||
'this render object has not yet been through layout, which typically '
|
'this render object has not yet been through layout, which typically '
|
||||||
'means that the size getter was called too early in the pipeline '
|
'means that the size getter was called too early in the pipeline '
|
||||||
'(e.g., during the build pahse) before the framework has determined '
|
'(e.g., during the build phase) before the framework has determined '
|
||||||
'the size and position of the render objects during layout.\n'
|
'the size and position of the render objects during layout.\n'
|
||||||
'The size getter was called for the following element:\n'
|
'The size getter was called for the following element:\n'
|
||||||
' $this\n'
|
' $this\n'
|
||||||
@ -2756,8 +2786,25 @@ abstract class Element implements BuildContext {
|
|||||||
Set<InheritedElement> _dependencies;
|
Set<InheritedElement> _dependencies;
|
||||||
bool _hadUnsatisfiedDependencies = false;
|
bool _hadUnsatisfiedDependencies = false;
|
||||||
|
|
||||||
|
bool _debugCheckStateIsActiveForAncestorLoopkup() {
|
||||||
|
assert(() {
|
||||||
|
if (_debugLifecycleState != _ElementLifecycle.active) {
|
||||||
|
throw new FlutterError(
|
||||||
|
'Looking up a deactivated widget\'s ancestor is unsafe.\n'
|
||||||
|
'At this point the state of the widget\'s element tree is no longer '
|
||||||
|
'stable. To safely refer to a widget\'s ancestor in its dispose() method, '
|
||||||
|
'save a reference to the ancestor by calling inheritFromWidgetOfExactType() '
|
||||||
|
'in the widget\'s didChangeDependencies() method.\n'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
|
InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
|
||||||
|
assert(_debugCheckStateIsActiveForAncestorLoopkup);
|
||||||
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
|
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
|
||||||
if (ancestor != null) {
|
if (ancestor != null) {
|
||||||
assert(ancestor is InheritedElement);
|
assert(ancestor is InheritedElement);
|
||||||
@ -2772,6 +2819,7 @@ abstract class Element implements BuildContext {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
|
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
|
||||||
|
assert(_debugCheckStateIsActiveForAncestorLoopkup);
|
||||||
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
|
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
|
||||||
return ancestor;
|
return ancestor;
|
||||||
}
|
}
|
||||||
@ -2783,6 +2831,7 @@ abstract class Element implements BuildContext {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget ancestorWidgetOfExactType(Type targetType) {
|
Widget ancestorWidgetOfExactType(Type targetType) {
|
||||||
|
assert(_debugCheckStateIsActiveForAncestorLoopkup);
|
||||||
Element ancestor = _parent;
|
Element ancestor = _parent;
|
||||||
while (ancestor != null && ancestor.widget.runtimeType != targetType)
|
while (ancestor != null && ancestor.widget.runtimeType != targetType)
|
||||||
ancestor = ancestor._parent;
|
ancestor = ancestor._parent;
|
||||||
@ -2791,6 +2840,7 @@ abstract class Element implements BuildContext {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
State ancestorStateOfType(TypeMatcher matcher) {
|
State ancestorStateOfType(TypeMatcher matcher) {
|
||||||
|
assert(_debugCheckStateIsActiveForAncestorLoopkup);
|
||||||
Element ancestor = _parent;
|
Element ancestor = _parent;
|
||||||
while (ancestor != null) {
|
while (ancestor != null) {
|
||||||
if (ancestor is StatefulElement && matcher.check(ancestor.state))
|
if (ancestor is StatefulElement && matcher.check(ancestor.state))
|
||||||
@ -2803,6 +2853,7 @@ abstract class Element implements BuildContext {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher) {
|
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher) {
|
||||||
|
assert(_debugCheckStateIsActiveForAncestorLoopkup);
|
||||||
Element ancestor = _parent;
|
Element ancestor = _parent;
|
||||||
while (ancestor != null) {
|
while (ancestor != null) {
|
||||||
if (ancestor is RenderObjectElement && matcher.check(ancestor.renderObject))
|
if (ancestor is RenderObjectElement && matcher.check(ancestor.renderObject))
|
||||||
@ -2815,6 +2866,7 @@ abstract class Element implements BuildContext {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void visitAncestorElements(bool visitor(Element element)) {
|
void visitAncestorElements(bool visitor(Element element)) {
|
||||||
|
assert(_debugCheckStateIsActiveForAncestorLoopkup);
|
||||||
Element ancestor = _parent;
|
Element ancestor = _parent;
|
||||||
while (ancestor != null && visitor(ancestor))
|
while (ancestor != null && visitor(ancestor))
|
||||||
ancestor = ancestor._parent;
|
ancestor = ancestor._parent;
|
||||||
|
122
packages/flutter/test/widgets/dispose_ancestor_lookup_test.dart
Normal file
122
packages/flutter/test/widgets/dispose_ancestor_lookup_test.dart
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart' hide TypeMatcher;
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
typedef void TestCallback(BuildContext context);
|
||||||
|
|
||||||
|
class TestWidget extends StatefulWidget {
|
||||||
|
TestWidget(this.callback);
|
||||||
|
|
||||||
|
final TestCallback callback;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TestWidgetState createState() => new TestWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestWidgetState extends State<TestWidget> {
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.callback(context);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => const Text('test');
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('inheritFromWidgetOfExactType() called from dispose() throws error', (WidgetTester tester) async {
|
||||||
|
bool disposeCalled = false;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new TestWidget((BuildContext context) {
|
||||||
|
disposeCalled = true;
|
||||||
|
context.inheritFromWidgetOfExactType(Container);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(new Container());
|
||||||
|
expect(disposeCalled, isTrue);
|
||||||
|
expect(tester.takeException(), isFlutterError);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ancestorInheritedElementForWidgetOfExactType() called from dispose() throws error', (WidgetTester tester) async {
|
||||||
|
bool disposeCalled = false;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new TestWidget((BuildContext context) {
|
||||||
|
disposeCalled = true;
|
||||||
|
context.ancestorInheritedElementForWidgetOfExactType(Container);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(new Container());
|
||||||
|
expect(disposeCalled, isTrue);
|
||||||
|
expect(tester.takeException(), isFlutterError);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ancestorWidgetOfExactType() called from dispose() throws error', (WidgetTester tester) async {
|
||||||
|
bool disposeCalled = false;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new TestWidget((BuildContext context) {
|
||||||
|
disposeCalled = true;
|
||||||
|
context.ancestorWidgetOfExactType(Container);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(new Container());
|
||||||
|
expect(disposeCalled, isTrue);
|
||||||
|
expect(tester.takeException(), isFlutterError);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ancestorStateOfType() called from dispose() throws error', (WidgetTester tester) async {
|
||||||
|
bool disposeCalled = false;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new TestWidget((BuildContext context) {
|
||||||
|
disposeCalled = true;
|
||||||
|
context.ancestorStateOfType(const TypeMatcher<Container>());
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(new Container());
|
||||||
|
expect(disposeCalled, isTrue);
|
||||||
|
expect(tester.takeException(), isFlutterError);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('ancestorRenderObjectOfType() called from dispose() throws error', (WidgetTester tester) async {
|
||||||
|
bool disposeCalled = false;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new TestWidget((BuildContext context) {
|
||||||
|
disposeCalled = true;
|
||||||
|
context.ancestorRenderObjectOfType(const TypeMatcher<Container>());
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(new Container());
|
||||||
|
expect(disposeCalled, isTrue);
|
||||||
|
expect(tester.takeException(), isFlutterError);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('visitAncestorElements() called from dispose() throws error', (WidgetTester tester) async {
|
||||||
|
bool disposeCalled = false;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new TestWidget((BuildContext context) {
|
||||||
|
disposeCalled = true;
|
||||||
|
context.visitAncestorElements((Element element){ });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(new Container());
|
||||||
|
expect(disposeCalled, isTrue);
|
||||||
|
expect(tester.takeException(), isFlutterError);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('dispose() method does not unconditionally throw an error', (WidgetTester tester) async {
|
||||||
|
bool disposeCalled = false;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new TestWidget((BuildContext context) {
|
||||||
|
disposeCalled = true;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(new Container());
|
||||||
|
expect(disposeCalled, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user