Unverified Commit aa5cc403 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Focus Manager debug output improvements. (#37891)

This improves the ability to debug focus manager issues. It's not meant to be used by developers unless they're debugging problems with the focus manager itself.
parent 19d113b0
......@@ -14,6 +14,24 @@ import 'focus_scope.dart';
import 'focus_traversal.dart';
import 'framework.dart';
// Used for debugging focus code. Set to true to see highly verbose debug output
// when focus changes occur.
const bool _kDebugFocus = false;
void _focusDebug(String message, [Iterable<String> details]) {
assert(() {
if (_kDebugFocus) {
debugPrint('FOCUS: $message');
if (details != null && details.isNotEmpty) {
for (String detail in details) {
debugPrint(' $detail');
}
}
}
return true;
}());
}
/// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey]
/// to receive key events.
///
......@@ -61,6 +79,7 @@ class FocusAttachment {
/// Calling [FocusNode.dispose] will also automatically detach the node.
void detach() {
assert(_node != null);
_focusDebug('Detaching node:', <String>[_node.toString(), 'With enclosing scope ${_node.enclosingScope}']);
if (isAttached) {
if (_node.hasPrimaryFocus) {
_node.unfocus();
......@@ -855,6 +874,7 @@ class FocusScopeNode extends FocusNode {
/// tree, the given scope must be a descendant of this scope.
void setFirstFocus(FocusScopeNode scope) {
assert(scope != null);
assert(scope != this, 'Unexpected self-reference in setFirstFocus.');
if (scope._parent == null) {
_reparent(scope);
}
......@@ -998,6 +1018,7 @@ class FocusManager with DiagnosticableTreeMixin {
// Called to indicate that the given node is being disposed.
void _willDisposeFocusNode(FocusNode node) {
assert(node != null);
_focusDebug('Disposing of node:', <String>[node.toString(), 'with enclosing scope ${node.enclosingScope}']);
_willUnfocusNode(node);
_dirtyNodes.remove(node);
}
......@@ -1006,6 +1027,7 @@ class FocusManager with DiagnosticableTreeMixin {
// pending request to be focused should be canceled.
void _willUnfocusNode(FocusNode node) {
assert(node != null);
_focusDebug('Unfocusing node $node');
if (_primaryFocus == node) {
_primaryFocus = null;
_dirtyNodes.add(node);
......@@ -1016,6 +1038,7 @@ class FocusManager with DiagnosticableTreeMixin {
_dirtyNodes.add(node);
_markNeedsUpdate();
}
_focusDebug('Unfocused node $node:', <String>['primary focus is $_primaryFocus', 'next focus will be $_nextFocus']);
}
// True indicates that there is an update pending.
......@@ -1027,6 +1050,7 @@ class FocusManager with DiagnosticableTreeMixin {
// If newFocus isn't specified, then don't mess with _nextFocus, just
// schedule the update.
_nextFocus = newFocus ?? _nextFocus;
_focusDebug('Scheduling update, next focus will be $_nextFocus');
if (_haveScheduledUpdate) {
return;
}
......@@ -1036,6 +1060,7 @@ class FocusManager with DiagnosticableTreeMixin {
void _applyFocusChange() {
_haveScheduledUpdate = false;
_focusDebug('Refreshing focus state. Next focus will be $_nextFocus');
final FocusNode previousFocus = _primaryFocus;
if (_primaryFocus == null && _nextFocus == null) {
// If we don't have any current focus, and nobody has asked to focus yet,
......@@ -1053,6 +1078,7 @@ class FocusManager with DiagnosticableTreeMixin {
_nextFocus = null;
}
if (previousFocus != _primaryFocus) {
_focusDebug('Updating focus from $previousFocus to $_primaryFocus');
if (previousFocus != null) {
_dirtyNodes.add(previousFocus);
}
......@@ -1060,10 +1086,17 @@ class FocusManager with DiagnosticableTreeMixin {
_dirtyNodes.add(_primaryFocus);
}
}
_focusDebug('Notifying ${_dirtyNodes.length} dirty nodes:', _dirtyNodes.toList().map<String>((FocusNode node) => node.toString()));
for (FocusNode node in _dirtyNodes) {
node._notify();
}
_dirtyNodes.clear();
assert(() {
if (_kDebugFocus) {
debugDumpFocusTree();
}
return true;
}());
}
@override
......@@ -1076,7 +1109,11 @@ class FocusManager with DiagnosticableTreeMixin {
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties.add(FlagProperty('haveScheduledUpdate', value: _haveScheduledUpdate, ifTrue: 'UPDATE SCHEDULED'));
properties.add(DiagnosticsProperty<FocusNode>('currentFocus', primaryFocus, defaultValue: null));
properties.add(DiagnosticsProperty<FocusNode>('primaryFocus', primaryFocus, defaultValue: null));
final Element element = primaryFocus?.context;
if (element != null) {
properties.add(DiagnosticsProperty<String>('primaryFocusCreator', element.debugGetCreatorChain(20)));
}
}
}
......
......@@ -367,21 +367,21 @@ void main() {
});
testWidgets('Unfocus works properly', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope1 = FocusScopeNode()..attach(context);
final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1')..attach(context);
final FocusAttachment scope1Attachment = scope1.attach(context);
final FocusScopeNode scope2 = FocusScopeNode();
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
final FocusAttachment scope2Attachment = scope2.attach(context);
final FocusNode parent1 = FocusNode();
final FocusNode parent1 = FocusNode(debugLabel: 'parent1');
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode();
final FocusNode parent2 = FocusNode(debugLabel: 'parent2');
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode();
final FocusNode child1 = FocusNode(debugLabel: 'child1');
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode();
final FocusNode child2 = FocusNode(debugLabel: 'child2');
final FocusAttachment child2Attachment = child2.attach(context);
final FocusNode child3 = FocusNode();
final FocusNode child3 = FocusNode(debugLabel: 'child3');
final FocusAttachment child3Attachment = child3.attach(context);
final FocusNode child4 = FocusNode();
final FocusNode child4 = FocusNode(debugLabel: 'child4');
final FocusAttachment child4Attachment = child4.attach(context);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -526,46 +526,47 @@ void main() {
description,
equalsIgnoringHashCodes(
'FocusManager#00000\n'
' │ currentFocus: FocusNode#00000\n'
' │\n'
' └─rootScope: FocusScopeNode#00000\n'
' │ FOCUSED\n'
' │ debugLabel: "Root Focus Scope"\n'
' │ focusedChild: FocusScopeNode#00000\n'
' │\n'
' ├─Child 1: FocusScopeNode#00000\n'
' │ │ context: Container-[GlobalKey#00000]\n'
' │ │ debugLabel: "Scope 1"\n'
' │ │\n'
' │ └─Child 1: FocusNode#00000\n'
' │ │ context: Container-[GlobalKey#00000]\n'
' │ │ debugLabel: "Parent 1"\n'
' │ │\n'
' │ ├─Child 1: FocusNode#00000\n'
' │ │ context: Container-[GlobalKey#00000]\n'
' │ │ debugLabel: "Child 1"\n'
' │ │\n'
' │ └─Child 2: FocusNode#00000\n'
' │ context: Container-[GlobalKey#00000]\n'
' │\n'
' └─Child 2: FocusScopeNode#00000\n'
' │ context: Container-[GlobalKey#00000]\n'
' │ FOCUSED\n'
' │ focusedChild: FocusNode#00000\n'
' │\n'
' └─Child 1: FocusNode#00000\n'
' │ context: Container-[GlobalKey#00000]\n'
' │ FOCUSED\n'
' │ debugLabel: "Parent 2"\n'
' │\n'
' ├─Child 1: FocusNode#00000\n'
' │ context: Container-[GlobalKey#00000]\n'
' │ debugLabel: "Child 3"\n'
' │\n'
' └─Child 2: FocusNode#00000\n'
' context: Container-[GlobalKey#00000]\n'
' FOCUSED\n'
' debugLabel: "Child 4"\n'
' │ primaryFocus: FocusNode#00000\n'
' │ primaryFocusCreator: Container-[GlobalKey#00000] ← [root]\n'
' │\n'
' └─rootScope: FocusScopeNode#00000\n'
' │ FOCUSED\n'
' │ debugLabel: "Root Focus Scope"\n'
' │ focusedChild: FocusScopeNode#00000\n'
' │\n'
' ├─Child 1: FocusScopeNode#00000\n'
' │ │ context: Container-[GlobalKey#00000]\n'
' │ │ debugLabel: "Scope 1"\n'
' │ │\n'
' │ └─Child 1: FocusNode#00000\n'
' │ │ context: Container-[GlobalKey#00000]\n'
' │ │ debugLabel: "Parent 1"\n'
' │ │\n'
' │ ├─Child 1: FocusNode#00000\n'
' │ │ context: Container-[GlobalKey#00000]\n'
' │ │ debugLabel: "Child 1"\n'
' │ │\n'
' │ └─Child 2: FocusNode#00000\n'
' │ context: Container-[GlobalKey#00000]\n'
' │\n'
' └─Child 2: FocusScopeNode#00000\n'
' │ context: Container-[GlobalKey#00000]\n'
' │ FOCUSED\n'
' │ focusedChild: FocusNode#00000\n'
' │\n'
' └─Child 1: FocusNode#00000\n'
' │ context: Container-[GlobalKey#00000]\n'
' │ FOCUSED\n'
' │ debugLabel: "Parent 2"\n'
' │\n'
' ├─Child 1: FocusNode#00000\n'
' │ context: Container-[GlobalKey#00000]\n'
' │ debugLabel: "Child 3"\n'
' │\n'
' └─Child 2: FocusNode#00000\n'
' context: Container-[GlobalKey#00000]\n'
' FOCUSED\n'
' debugLabel: "Child 4"\n'
));
});
});
......
......@@ -718,6 +718,7 @@ void main() {
child: Column(
children: <Widget>[
TestFocus(
debugLabel: 'Child B',
key: keyB,
name: 'b',
autofocus: true,
......@@ -799,6 +800,7 @@ void main() {
child: Column(
children: <Widget>[
TestFocus(
debugLabel: 'Child B',
key: keyB,
name: 'b',
autofocus: true,
......
......@@ -811,15 +811,9 @@ void main() {
debugLabel: 'Scope',
child: Column(
children: <Widget>[
Focus(
focusNode: focusTop,
child: Container(width: 100, height: 100)),
Focus(
focusNode: focusCenter,
child: Container(width: 100, height: 100)),
Focus(
focusNode: focusBottom,
child: Container(width: 100, height: 100)),
Focus(focusNode: focusTop, child: Container(width: 100, height: 100)),
Focus(focusNode: focusCenter, child: Container(width: 100, height: 100)),
Focus(focusNode: focusBottom, child: Container(width: 100, height: 100)),
],
),
),
......@@ -841,12 +835,8 @@ void main() {
debugLabel: 'Scope',
child: Column(
children: <Widget>[
Focus(
focusNode: focusTop,
child: Container(width: 100, height: 100)),
Focus(
focusNode: focusBottom,
child: Container(width: 100, height: 100)),
Focus(focusNode: focusTop, child: Container(width: 100, height: 100)),
Focus(focusNode: focusBottom, child: Container(width: 100, height: 100)),
],
),
),
......
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