Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
b52d6594
Commit
b52d6594
authored
Apr 17, 2017
by
Hans Muller
Committed by
GitHub
Apr 17, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Disallow unsafe ancestor methods (#9425)
parent
acd75c0a
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
176 additions
and
2 deletions
+176
-2
framework.dart
packages/flutter/lib/src/widgets/framework.dart
+54
-2
dispose_ancestor_lookup_test.dart
...es/flutter/test/widgets/dispose_ancestor_lookup_test.dart
+122
-0
No files found.
packages/flutter/lib/src/widgets/framework.dart
View file @
b52d6594
...
...
@@ -1585,6 +1585,11 @@ abstract class BuildContext {
/// (directly or indirectly) from build methods, layout and paint callbacks, or
/// 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.
/// gesture callbacks) or timers, to obtain a value once, if that value is not
/// going to be cached and reused later.
...
...
@@ -1609,6 +1614,11 @@ abstract class BuildContext {
/// widgets to obtain their corresponding [InheritedElement] object so that they
/// can call [InheritedElement.dispatchDidChangeDependencies] to actually
/// 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
);
/// 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
/// tree). Only call this method if the distance from this widget to the
/// 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
);
/// 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
/// not be rebuilt if the value that would be returned by this method changes.
/// 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
/// scroll this build context's widget into view, or to move the focus in
/// response to user interaction.
...
...
@@ -1645,6 +1660,11 @@ abstract class BuildContext {
/// tree). Only call this method if the distance from this widget to the
/// 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:
///
/// ```dart
...
...
@@ -1663,6 +1683,11 @@ abstract class BuildContext {
/// it is used by [Material] so that [InkWell] widgets can trigger the ink
/// 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
/// tree). Only call this method if the distance from this widget to the
/// desired ancestor is known to be small and bounded.
...
...
@@ -1677,6 +1702,11 @@ abstract class BuildContext {
/// This is useful for inspecting the widget 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
));
/// 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 '
'this render object has not yet been through layout, which typically '
'means that the size getter was called too early in the pipeline '
'(e.g., during the build p
ah
se) before the framework has determined '
'(e.g., during the build p
ha
se) before the framework has determined '
'the size and position of the render objects during layout.
\n
'
'The size getter was called for the following element:
\n
'
'
$this
\n
'
...
...
@@ -2756,8 +2786,25 @@ abstract class Element implements BuildContext {
Set
<
InheritedElement
>
_dependencies
;
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
InheritedWidget
inheritFromWidgetOfExactType
(
Type
targetType
)
{
assert
(
_debugCheckStateIsActiveForAncestorLoopkup
);
final
InheritedElement
ancestor
=
_inheritedWidgets
==
null
?
null
:
_inheritedWidgets
[
targetType
];
if
(
ancestor
!=
null
)
{
assert
(
ancestor
is
InheritedElement
);
...
...
@@ -2772,6 +2819,7 @@ abstract class Element implements BuildContext {
@override
InheritedElement
ancestorInheritedElementForWidgetOfExactType
(
Type
targetType
)
{
assert
(
_debugCheckStateIsActiveForAncestorLoopkup
);
final
InheritedElement
ancestor
=
_inheritedWidgets
==
null
?
null
:
_inheritedWidgets
[
targetType
];
return
ancestor
;
}
...
...
@@ -2783,6 +2831,7 @@ abstract class Element implements BuildContext {
@override
Widget
ancestorWidgetOfExactType
(
Type
targetType
)
{
assert
(
_debugCheckStateIsActiveForAncestorLoopkup
);
Element
ancestor
=
_parent
;
while
(
ancestor
!=
null
&&
ancestor
.
widget
.
runtimeType
!=
targetType
)
ancestor
=
ancestor
.
_parent
;
...
...
@@ -2791,6 +2840,7 @@ abstract class Element implements BuildContext {
@override
State
ancestorStateOfType
(
TypeMatcher
matcher
)
{
assert
(
_debugCheckStateIsActiveForAncestorLoopkup
);
Element
ancestor
=
_parent
;
while
(
ancestor
!=
null
)
{
if
(
ancestor
is
StatefulElement
&&
matcher
.
check
(
ancestor
.
state
))
...
...
@@ -2803,6 +2853,7 @@ abstract class Element implements BuildContext {
@override
RenderObject
ancestorRenderObjectOfType
(
TypeMatcher
matcher
)
{
assert
(
_debugCheckStateIsActiveForAncestorLoopkup
);
Element
ancestor
=
_parent
;
while
(
ancestor
!=
null
)
{
if
(
ancestor
is
RenderObjectElement
&&
matcher
.
check
(
ancestor
.
renderObject
))
...
...
@@ -2815,6 +2866,7 @@ abstract class Element implements BuildContext {
@override
void
visitAncestorElements
(
bool
visitor
(
Element
element
))
{
assert
(
_debugCheckStateIsActiveForAncestorLoopkup
);
Element
ancestor
=
_parent
;
while
(
ancestor
!=
null
&&
visitor
(
ancestor
))
ancestor
=
ancestor
.
_parent
;
...
...
packages/flutter/test/widgets/dispose_ancestor_lookup_test.dart
0 → 100644
View file @
b52d6594
// 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
);
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment