Unverified Commit 835c7fab authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

Fix FocusManager constructor (#75894)

The FocusManager constructor was registering global event handlers
on the shared RawKeyboard instance and the global pointer router.
This posed a few problems: (1) there was no way to unregister these
handlers, and (2) instantiating a second FocusManager would overwrite
the existing focus manager's RawKeyboard handler. This was manifesting
in unexpected ways, such as the fact that constructing a second
BuildOwner (for a parallel tree, for instance) was obliterating the
event handler for the main BuildOwner's focus manager, thus messing
with focus.

This change separates those global event registrations into a
dedicated method, registerGlobalHandlers(), and overrides dispose()
to properly unregister those handlers.
parent db038a1d
......@@ -33,7 +33,6 @@ class Rectangle extends StatelessWidget {
double? value;
RenderObjectToWidgetElement<RenderBox>? element;
BuildOwner owner = BuildOwner();
void attachWidgetTreeToRenderTree(RenderProxyBox container) {
element = RenderObjectToWidgetAdapter<RenderBox>(
container: container,
......@@ -74,7 +73,7 @@ void attachWidgetTreeToRenderTree(RenderProxyBox container) {
),
),
),
).attachToRenderTree(owner, element);
).attachToRenderTree(WidgetsBinding.instance!.buildOwner!, element);
}
Duration? timeBase;
......@@ -87,7 +86,7 @@ void rotate(Duration timeStamp) {
transformBox.setIdentity();
transformBox.rotateZ(delta);
owner.buildScope(element!);
WidgetsBinding.instance!.buildOwner!.buildScope(element!);
}
void main() {
......
......@@ -1439,12 +1439,39 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
/// This constructor is rarely called directly. To access the [FocusManager],
/// consider using the [FocusManager.instance] accessor instead (which gets it
/// from the [WidgetsBinding] singleton).
///
/// This newly constructed focus manager does not have the necessary event
/// handlers registered to allow it to manage focus. To register those event
/// handlers, callers must call [registerGlobalHandlers]. See the
/// documentation in that method for caveats to watch out for.
FocusManager() {
rootScope._manager = this;
}
/// Registers global input event handlers that are needed to manage focus.
///
/// This sets the [RawKeyboard.keyEventHandler] for the shared instance of
/// [RawKeyboard] and adds a route to the global entry in the gesture routing
/// table. As such, only one [FocusManager] instance should register its
/// global handlers.
///
/// When this focus manager is no longer needed, calling [dispose] on it will
/// unregister these handlers.
void registerGlobalHandlers() {
assert(RawKeyboard.instance.keyEventHandler == null);
RawKeyboard.instance.keyEventHandler = _handleRawKeyEvent;
GestureBinding.instance!.pointerRouter.addGlobalRoute(_handlePointerEvent);
}
@override
void dispose() {
if (RawKeyboard.instance.keyEventHandler == _handleRawKeyEvent) {
RawKeyboard.instance.keyEventHandler = null;
GestureBinding.instance!.pointerRouter.removeGlobalRoute(_handlePointerEvent);
}
super.dispose();
}
/// Provides convenient access to the current [FocusManager] singleton from
/// the [WidgetsBinding] instance.
static FocusManager get instance => WidgetsBinding.instance!.focusManager;
......
......@@ -2327,7 +2327,7 @@ abstract class BuildContext {
/// Size measureWidget(Widget widget) {
/// final PipelineOwner pipelineOwner = PipelineOwner();
/// final MeasurementView rootView = pipelineOwner.rootNode = MeasurementView();
/// final BuildOwner buildOwner = BuildOwner(focusManager: FailingFocusManager());
/// final BuildOwner buildOwner = BuildOwner(focusManager: FocusManager());
/// final RenderObjectToWidgetElement<RenderBox> element = RenderObjectToWidgetAdapter<RenderBox>(
/// container: rootView,
/// debugShortDescription: '[root]',
......@@ -2344,17 +2344,6 @@ abstract class BuildContext {
/// }
/// }
///
/// // The default FocusManager, when created, modifies some static properties
/// // that we don't want to modify, which is why we use a failing implementation
/// // here.
/// class FailingFocusManager implements FocusManager {
/// @override
/// dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
///
/// @override
/// String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) => 'FailingFocusManager';
/// }
///
/// class MeasurementView extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
/// @override
/// void performLayout() {
......@@ -2370,8 +2359,14 @@ abstract class BuildContext {
/// {@end-tool}
class BuildOwner {
/// Creates an object that manages widgets.
///
/// If the `focusManager` argument is not specified or is null, this will
/// construct a new [FocusManager] and register its global input handlers
/// via [FocusManager.registerGlobalHandlers], which will modify static
/// state. Callers wishing to avoid altering this state can explicitly pass
/// a focus manager here.
BuildOwner({ this.onBuildScheduled, FocusManager? focusManager }) :
focusManager = focusManager ?? FocusManager();
focusManager = focusManager ?? (FocusManager()..registerGlobalHandlers());
/// Called on each build pass when the first buildable element is marked
/// dirty.
......@@ -2402,6 +2397,12 @@ class BuildOwner {
/// the [FocusScopeNode] for a given [BuildContext].
///
/// See [FocusManager] for more details.
///
/// This field will default to a [FocusManager] that has registered its
/// global input handlers via [FocusManager.registerGlobalHandlers]. Callers
/// wishing to avoid registering those handlers (and modifying the associated
/// static state) can explicitly pass a focus manager to the [new BuildOwner]
/// constructor.
FocusManager focusManager;
/// Adds an element to the dirty elements list so that it will be rebuilt
......
......@@ -1490,7 +1490,7 @@ void main() {
final int pointerRouterCount = GestureBinding.instance!.pointerRouter.debugGlobalRouteCount;
final RawKeyEventHandler? rawKeyEventHandler = RawKeyboard.instance.keyEventHandler;
expect(rawKeyEventHandler, isNotNull);
BuildOwner(focusManager: _FakeFocusManager());
BuildOwner(focusManager: FocusManager());
expect(GestureBinding.instance!.pointerRouter.debugGlobalRouteCount, pointerRouterCount);
expect(RawKeyboard.instance.keyEventHandler, same(rawKeyEventHandler));
});
......@@ -1516,18 +1516,6 @@ void main() {
});
}
class _FakeFocusManager implements FocusManager {
@override
dynamic noSuchMethod(Invocation invocation) {
return super.noSuchMethod(invocation);
}
@override
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return '_FakeFocusManager';
}
}
class _WidgetWithNoVisitChildren extends StatelessWidget {
const _WidgetWithNoVisitChildren(this.child, { Key? key }) :
super(key: key);
......
......@@ -54,7 +54,7 @@ class OffscreenWidgetTree {
}
final RenderView renderView = OffscreenRenderView();
final BuildOwner buildOwner = BuildOwner();
final BuildOwner buildOwner = BuildOwner(focusManager: FocusManager());
final PipelineOwner pipelineOwner = PipelineOwner();
RenderObjectToWidgetElement<RenderBox>? root;
......
......@@ -897,7 +897,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
FlutterError.demangleStackTrace = _oldStackTraceDemangler;
_pendingExceptionDetails = null;
_parentZone = null;
buildOwner!.focusManager = FocusManager();
buildOwner!.focusManager.dispose();
buildOwner!.focusManager = FocusManager()..registerGlobalHandlers();
// Disabling the warning because @visibleForTesting doesn't take the testing
// framework itself into account, but we don't want it visible outside of
// tests.
......
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