From 83a84e7416d9cbeb2b34c90234c164fcf30e3e69 Mon Sep 17 00:00:00 2001 From: Juan Tugores Date: Fri, 8 Mar 2024 16:39:03 -0800 Subject: [PATCH 1/3] Implement PlatformDispatcher.requestViewFocusChange on web --- .../lib/src/engine/platform_dispatcher.dart | 7 ++- .../view_focus_binding_test.dart | 60 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 20588af1054f7..0144b0550e820 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -257,7 +257,12 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { required ui.ViewFocusState state, required ui.ViewFocusDirection direction, }) { - // TODO(tugorez): implement this method. At the moment will be a no op call. + final DomElement? viewElement = viewManager[viewId]?.dom.rootElement; + if (state == ui.ViewFocusState.focused) { + viewElement?.focus(); + } else { + viewElement?.blur(); + } } /// A set of views which have rendered in the current `onBeginFrame` or diff --git a/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart b/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart index 8610d394fff90..ce35a8990fa67 100644 --- a/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart +++ b/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart @@ -164,6 +164,66 @@ void testMain() { expect(dispatchedViewFocusEvents[2].state, ui.ViewFocusState.unfocused); expect(dispatchedViewFocusEvents[2].direction, ui.ViewFocusDirection.undefined); }); + + test('requestViewFocusChange focuses the view', () { + final EngineFlutterView view = createAndRegisterView(dispatcher); + + dispatcher.requestViewFocusChange( + viewId: view.viewId, + state: ui.ViewFocusState.focused, + direction: ui.ViewFocusDirection.forward, + ); + + expect(domDocument.activeElement, view.dom.rootElement); + + expect(dispatchedViewFocusEvents, hasLength(1)); + + expect(dispatchedViewFocusEvents[0].viewId, view.viewId); + expect(dispatchedViewFocusEvents[0].state, ui.ViewFocusState.focused); + expect(dispatchedViewFocusEvents[0].direction, ui.ViewFocusDirection.forward); + }); + + test('requestViewFocusChange blurs the view', () { + final EngineFlutterView view = createAndRegisterView(dispatcher); + + dispatcher.requestViewFocusChange( + viewId: view.viewId, + state: ui.ViewFocusState.focused, + direction: ui.ViewFocusDirection.forward, + ); + + dispatcher.requestViewFocusChange( + viewId: view.viewId, + state: ui.ViewFocusState.unfocused, + direction: ui.ViewFocusDirection.undefined, + ); + + expect(domDocument.activeElement, isNot(view.dom.rootElement)); + + expect(dispatchedViewFocusEvents, hasLength(2)); + + expect(dispatchedViewFocusEvents[0].viewId, view.viewId); + expect(dispatchedViewFocusEvents[0].state, ui.ViewFocusState.focused); + expect(dispatchedViewFocusEvents[0].direction, ui.ViewFocusDirection.forward); + + expect(dispatchedViewFocusEvents[1].viewId, view.viewId); + expect(dispatchedViewFocusEvents[1].state, ui.ViewFocusState.unfocused); + expect(dispatchedViewFocusEvents[1].direction, ui.ViewFocusDirection.undefined); + }); + + + test('requestViewFocusChange does nothing if the view does not exist', () { + final EngineFlutterView view = createAndRegisterView(dispatcher); + + dispatcher.requestViewFocusChange( + viewId: 5094555, + state: ui.ViewFocusState.focused, + direction: ui.ViewFocusDirection.forward, + ); + + expect(domDocument.activeElement, isNot(view.dom.rootElement)); + expect(dispatchedViewFocusEvents, isEmpty); + }); }); } From 20c4186e08b739f5eb01734d5b8b228a2936822b Mon Sep 17 00:00:00 2001 From: Juan Tugores Date: Fri, 8 Mar 2024 17:07:42 -0800 Subject: [PATCH 2/3] Do not move the browser focus to the view if another dom node (inside it) already has it --- .../lib/src/engine/platform_dispatcher.dart | 7 +-- .../view_focus_binding.dart | 12 +++++ .../view_focus_binding_test.dart | 44 ++++++++++++++++++- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 0144b0550e820..868ceab4c8dcd 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -257,12 +257,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { required ui.ViewFocusState state, required ui.ViewFocusDirection direction, }) { - final DomElement? viewElement = viewManager[viewId]?.dom.rootElement; - if (state == ui.ViewFocusState.focused) { - viewElement?.focus(); - } else { - viewElement?.blur(); - } + _viewFocusBinding.changeViewFocus(viewId, state); } /// A set of views which have rendered in the current `onBeginFrame` or diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart b/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart index 9f9852bd48ed8..1f17f151d5ce4 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart @@ -34,6 +34,18 @@ final class ViewFocusBinding { _onViewCreatedListener?.cancel(); } + void changeViewFocus(int viewId, ui.ViewFocusState state) { + final DomElement? viewElement = _viewManager[viewId]?.dom.rootElement; + + if (state == ui.ViewFocusState.focused) { + if (viewId != _viewId(domDocument.activeElement)) { + viewElement?.focus(); + } + } else { + viewElement?.blur(); + } + } + late final DomEventListener _handleFocusin = createDomEventListener((DomEvent event) { event as DomFocusEvent; _handleFocusChange(event.target as DomElement?); diff --git a/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart b/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart index ce35a8990fa67..419de687ac66e 100644 --- a/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart +++ b/lib/web_ui/test/engine/platform_dispatcher/view_focus_binding_test.dart @@ -211,7 +211,6 @@ void testMain() { expect(dispatchedViewFocusEvents[1].direction, ui.ViewFocusDirection.undefined); }); - test('requestViewFocusChange does nothing if the view does not exist', () { final EngineFlutterView view = createAndRegisterView(dispatcher); @@ -224,6 +223,49 @@ void testMain() { expect(domDocument.activeElement, isNot(view.dom.rootElement)); expect(dispatchedViewFocusEvents, isEmpty); }); + + test('requestViewFocusChange does nothing if the view is already focused', () { + final EngineFlutterView view = createAndRegisterView(dispatcher); + + dispatcher.requestViewFocusChange( + viewId: view.viewId, + state: ui.ViewFocusState.focused, + direction: ui.ViewFocusDirection.forward, + ); + dispatcher.requestViewFocusChange( + viewId: view.viewId, + state: ui.ViewFocusState.focused, + direction: ui.ViewFocusDirection.forward, + ); + + expect(dispatchedViewFocusEvents, hasLength(1)); + + expect(dispatchedViewFocusEvents[0].viewId, view.viewId); + expect(dispatchedViewFocusEvents[0].state, ui.ViewFocusState.focused); + expect(dispatchedViewFocusEvents[0].direction, ui.ViewFocusDirection.forward); + }); + + test('requestViewFocusChange does not move the focus to the view', () { + final DomElement input = createDomElement('input'); + final EngineFlutterView view = createAndRegisterView(dispatcher); + + view.dom.rootElement.append(input); + input.focus(); + + dispatcher.requestViewFocusChange( + viewId: view.viewId, + state: ui.ViewFocusState.focused, + direction: ui.ViewFocusDirection.forward, + ); + + expect(domDocument.activeElement, input); + + expect(dispatchedViewFocusEvents, hasLength(1)); + + expect(dispatchedViewFocusEvents[0].viewId, view.viewId); + expect(dispatchedViewFocusEvents[0].state, ui.ViewFocusState.focused); + expect(dispatchedViewFocusEvents[0].direction, ui.ViewFocusDirection.forward); + }); }); } From b25ad80625bdb5fc8483e1dee6434bbfb1d69175 Mon Sep 17 00:00:00 2001 From: Juan Tugores Date: Fri, 8 Mar 2024 17:11:15 -0800 Subject: [PATCH 3/3] Add a comment. --- .../lib/src/engine/platform_dispatcher/view_focus_binding.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart b/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart index 1f17f151d5ce4..1dc10b9fef846 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart @@ -38,6 +38,7 @@ final class ViewFocusBinding { final DomElement? viewElement = _viewManager[viewId]?.dom.rootElement; if (state == ui.ViewFocusState.focused) { + // Only move the focus to the flutter view if nothing inside it is focused already. if (viewId != _viewId(domDocument.activeElement)) { viewElement?.focus(); }