From aa5cc403688da10c6da2f5c67a7693a71cb1aa09 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 12 Aug 2019 13:02:49 -0700 Subject: [PATCH] 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. --- .../lib/src/widgets/focus_manager.dart | 39 +++++++- .../test/widgets/focus_manager_test.dart | 97 ++++++++++--------- .../test/widgets/focus_scope_test.dart | 2 + .../test/widgets/focus_traversal_test.dart | 20 +--- 4 files changed, 94 insertions(+), 64 deletions(-) diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index 5f00e0610c..742ddc77db 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -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 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:', [_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:', [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:', ['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((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('currentFocus', primaryFocus, defaultValue: null)); + properties.add(DiagnosticsProperty('primaryFocus', primaryFocus, defaultValue: null)); + final Element element = primaryFocus?.context; + if (element != null) { + properties.add(DiagnosticsProperty('primaryFocusCreator', element.debugGetCreatorChain(20))); + } } } diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart index 7bb4033b5b..0db8db5fab 100644 --- a/packages/flutter/test/widgets/focus_manager_test.dart +++ b/packages/flutter/test/widgets/focus_manager_test.dart @@ -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' )); }); }); diff --git a/packages/flutter/test/widgets/focus_scope_test.dart b/packages/flutter/test/widgets/focus_scope_test.dart index 04faedba04..82f4b06936 100644 --- a/packages/flutter/test/widgets/focus_scope_test.dart +++ b/packages/flutter/test/widgets/focus_scope_test.dart @@ -718,6 +718,7 @@ void main() { child: Column( children: [ TestFocus( + debugLabel: 'Child B', key: keyB, name: 'b', autofocus: true, @@ -799,6 +800,7 @@ void main() { child: Column( children: [ TestFocus( + debugLabel: 'Child B', key: keyB, name: 'b', autofocus: true, diff --git a/packages/flutter/test/widgets/focus_traversal_test.dart b/packages/flutter/test/widgets/focus_traversal_test.dart index deddeaac6a..ecec16ebb1 100644 --- a/packages/flutter/test/widgets/focus_traversal_test.dart +++ b/packages/flutter/test/widgets/focus_traversal_test.dart @@ -811,15 +811,9 @@ void main() { debugLabel: 'Scope', child: Column( children: [ - 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: [ - 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)), ], ), ),