From 7afe40650a7291582ffca1074a7d1c41da05fc1e Mon Sep 17 00:00:00 2001 From: Juan Tugores Date: Wed, 4 Sep 2024 19:26:01 -0700 Subject: [PATCH 1/2] focusout works when focus changes in a blur call. --- lib/web_ui/lib/src/engine/dom.dart | 4 ++++ .../view_focus_binding.dart | 9 +++++++ .../view_focus_binding_test.dart | 24 +++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index d3f422727885e..5fb335f11b728 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -339,6 +339,10 @@ extension DomHTMLDocumentExtension on DomHTMLDocument { @JS('visibilityState') external JSString get _visibilityState; String get visibilityState => _visibilityState.toDart; + + @JS('hasFocus') + external JSBoolean _hasFocus(); + bool hasFocus() => _hasFocus().toDart; } @JS('document') 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 54b71a9d07694..0e3efa2af91c4 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 @@ -64,6 +64,15 @@ final class ViewFocusBinding { }); late final DomEventListener _handleFocusout = createDomEventListener((DomEvent event) { + // During focusout processing, activeElement typically points to . + // However, if an element is focused during a blur event, activeElement points to that focused element. + // We leverage this behavior to ignore focusout events where the document has focus but activeElement is not . + // + // Refer to https://github.com/flutter/engine/pull/54965 for more info. + if (domDocument.hasFocus() && domDocument.activeElement != domDocument.body) { + return; + } + event as DomFocusEvent; _handleFocusChange(event.relatedTarget 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 883176cef9235..fe0f9c5479ea3 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 @@ -270,6 +270,30 @@ void testMain() { expect(dispatchedViewFocusEvents[0].state, ui.ViewFocusState.focused); expect(dispatchedViewFocusEvents[0].direction, ui.ViewFocusDirection.forward); }); + + test('works even if focus is changed in the middle of a blur call', () { + final DomElement input1 = createDomElement('input'); + final DomElement input2 = createDomElement('input'); + final EngineFlutterView view = createAndRegisterView(dispatcher); + final DomEventListener focusInput1Listener = createDomEventListener((DomEvent event) { + input1.focusWithoutScroll(); + }); + + view.dom.rootElement.append(input1); + view.dom.rootElement.append(input2); + + input1.addEventListener('blur', focusInput1Listener); + input1.focusWithoutScroll(); + // The event handler above should move the focus back to input1. + input2.focusWithoutScroll(); + input1.removeEventListener('blur', focusInput1Listener); + + 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 192bdb49ed598c82d78715e1db31c20f7a8796ee Mon Sep 17 00:00:00 2001 From: Juanjo Tugores Date: Thu, 5 Sep 2024 10:33:35 -0700 Subject: [PATCH 2/2] Update lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart Co-authored-by: Mouad Debbar --- .../lib/src/engine/platform_dispatcher/view_focus_binding.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 0e3efa2af91c4..97aa20e408216 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 @@ -69,7 +69,8 @@ final class ViewFocusBinding { // We leverage this behavior to ignore focusout events where the document has focus but activeElement is not . // // Refer to https://github.com/flutter/engine/pull/54965 for more info. - if (domDocument.hasFocus() && domDocument.activeElement != domDocument.body) { + final bool wasFocusInvoked = domDocument.hasFocus() && domDocument.activeElement != domDocument.body; + if (wasFocusInvoked) { return; }