From b025f5cb460792cd3dbeb69a9106c8dbeee270c5 Mon Sep 17 00:00:00 2001 From: Joe Vilches Date: Tue, 14 Jan 2025 11:35:51 -0800 Subject: [PATCH] Allow text inputs to handle native requestFocus calls (#48547) Summary: For some unknown reason, we have been swallowing [`requestFocus`](https://developer.android.com/reference/android/view/View#requestFocus(int) calls since `TextInput` is a controlled component - meaning you can control this components value and focus state from JS. This decision was originally made pre 2015 and I cannot find the reason why I do not think this makes sense. We can still request focus from JS, while allowing the OS to request focus as well in certain cases and we would still be controlling this text input. This is breaking keyboard navigation. Pressing tab or arrow keys will no-op if the next destination is a `TextInput`. This is because Android will call `requestFocus` from [here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/ViewRootImpl.java;l=7868?q=performFocusNavigation) when handling key events. Notably, Explore By Touch (TalkBack) swiping gestures WOULD focus `TextInputs` since they go through `ExploreByTouchHelper` methods which we override to call the proper `requestFocusInternal()` method. **In this diff**: I move the logic in `requestFocusInternal()` into `requestFocus`. Reviewed By: NickGerleman Differential Revision: D67953398 --- .../featureflags/ReactNativeFeatureFlags.kt | 8 +++- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 ++++- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +++++- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../react/views/textinput/ReactEditText.java | 44 ++++++++++++++++--- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 ++++++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 ++- .../textinput/ReactTextInputPropertyTest.kt | 2 + .../featureflags/ReactNativeFeatureFlags.cpp | 6 ++- .../featureflags/ReactNativeFeatureFlags.h | 7 ++- .../ReactNativeFeatureFlagsAccessor.cpp | 38 +++++++++++----- .../ReactNativeFeatureFlagsAccessor.h | 6 ++- .../ReactNativeFeatureFlagsDefaults.h | 6 ++- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 ++++- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 ++- .../NativeReactNativeFeatureFlags.h | 4 +- .../ReactNativeFeatureFlags.config.js | 9 ++++ .../featureflags/ReactNativeFeatureFlags.js | 7 ++- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 22 files changed, 184 insertions(+), 35 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 53c4bd6c186d09..ee749afde3cbeb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3e10f8d2f623da3b7b502d8fa78f82a4>> + * @generated SignedSource<<2c321a7a8e811dc238a75f76180843b9>> */ /** @@ -244,6 +244,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun useAlwaysAvailableJSErrorHandling(): Boolean = accessor.useAlwaysAvailableJSErrorHandling() + /** + * If true, focusing in ReactEditText will mainly use stock Android requestFocus() behavior. If false it will use legacy custom focus behavior. + */ + @JvmStatic + public fun useEditTextStockAndroidFocusBehavior(): Boolean = accessor.useEditTextStockAndroidFocusBehavior() + /** * Should this application enable the Fabric Interop Layer for Android? If yes, the application will behave so that it can accept non-Fabric components and render them on Fabric. This toggle is controlling extra logic such as custom event dispatching that are needed for the Fabric Interop Layer to work correctly. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index c25a7ad0a77d46..3a7fbd9185358c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<497bbb23778fe0f9763e9bfa715ea3aa>> + * @generated SignedSource<> */ /** @@ -56,6 +56,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso private var loadVectorDrawablesOnImagesCache: Boolean? = null private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null + private var useEditTextStockAndroidFocusBehaviorCache: Boolean? = null private var useFabricInteropCache: Boolean? = null private var useImmediateExecutorInAndroidBridgelessCache: Boolean? = null private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null @@ -390,6 +391,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso return cached } + override fun useEditTextStockAndroidFocusBehavior(): Boolean { + var cached = useEditTextStockAndroidFocusBehaviorCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.useEditTextStockAndroidFocusBehavior() + useEditTextStockAndroidFocusBehaviorCache = cached + } + return cached + } + override fun useFabricInterop(): Boolean { var cached = useFabricInteropCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 181c06d9c7cb03..d29a6979cabd9b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -100,6 +100,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun useAlwaysAvailableJSErrorHandling(): Boolean + @DoNotStrip @JvmStatic public external fun useEditTextStockAndroidFocusBehavior(): Boolean + @DoNotStrip @JvmStatic public external fun useFabricInterop(): Boolean @DoNotStrip @JvmStatic public external fun useImmediateExecutorInAndroidBridgeless(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index ca2265e22368bb..882730b504259b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<5de2cfc00f486b7d07266939ce18a397>> + * @generated SignedSource<<85d40be44d053b58a74ad76467c8e5e9>> */ /** @@ -95,6 +95,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun useAlwaysAvailableJSErrorHandling(): Boolean = false + override fun useEditTextStockAndroidFocusBehavior(): Boolean = true + override fun useFabricInterop(): Boolean = false override fun useImmediateExecutorInAndroidBridgeless(): Boolean = true diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 0200670573d136..f08720d444f56d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<18d5c2ffa66a36e364bc358a144534ec>> */ /** @@ -60,6 +60,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces private var loadVectorDrawablesOnImagesCache: Boolean? = null private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null + private var useEditTextStockAndroidFocusBehaviorCache: Boolean? = null private var useFabricInteropCache: Boolean? = null private var useImmediateExecutorInAndroidBridgelessCache: Boolean? = null private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null @@ -430,6 +431,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun useEditTextStockAndroidFocusBehavior(): Boolean { + var cached = useEditTextStockAndroidFocusBehaviorCache + if (cached == null) { + cached = currentProvider.useEditTextStockAndroidFocusBehavior() + accessedFeatureFlags.add("useEditTextStockAndroidFocusBehavior") + useEditTextStockAndroidFocusBehaviorCache = cached + } + return cached + } + override fun useFabricInterop(): Boolean { var cached = useFabricInteropCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index e3fb3270f52fce..cbff3e1f929001 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3cd802bdd1d383ea0668e43319d53b3f>> + * @generated SignedSource<<4f90c47ea6f23c2ebfae1f35790c4af5>> */ /** @@ -95,6 +95,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun useAlwaysAvailableJSErrorHandling(): Boolean + @DoNotStrip public fun useEditTextStockAndroidFocusBehavior(): Boolean + @DoNotStrip public fun useFabricInterop(): Boolean @DoNotStrip public fun useImmediateExecutorInAndroidBridgeless(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index a0370775f5c288..2282e43bd21d01 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -50,6 +50,7 @@ import com.facebook.react.bridge.ReactSoftExceptionLogger; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.build.ReactBuildConfig; +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags; import com.facebook.react.uimanager.BackgroundStyleApplicator; import com.facebook.react.uimanager.LengthPercentage; import com.facebook.react.uimanager.LengthPercentageType; @@ -145,7 +146,9 @@ public class ReactEditText extends AppCompatEditText { public ReactEditText(Context context) { super(context); - setFocusableInTouchMode(false); + if (!ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior()) { + setFocusableInTouchMode(false); + } mInputMethodManager = (InputMethodManager) @@ -188,7 +191,9 @@ public boolean performAccessibilityAction(View host, int action, Bundle args) { // selection on accessibility click to undo that. setSelection(length); } - return requestFocusInternal(); + return ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior() + ? requestFocusProgramatically() + : requestFocusInternal(); } return super.performAccessibilityAction(host, action, args); } @@ -338,7 +343,10 @@ public boolean onTextContextMenuItem(int id) { @Override public void clearFocus() { - setFocusableInTouchMode(false); + boolean useStockFocusBehavior = ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior(); + if (!useStockFocusBehavior) { + setFocusableInTouchMode(false); + } super.clearFocus(); hideSoftKeyboard(); } @@ -349,7 +357,9 @@ public boolean requestFocus(int direction, Rect previouslyFocusedRect) { // is a controlled component, which means its focus is controlled by JS, with two exceptions: // autofocus when it's attached to the window, and responding to accessibility events. In both // of these cases, we call requestFocusInternal() directly. - return isFocused(); + return ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior() + ? super.requestFocus(direction, previouslyFocusedRect) + : isFocused(); } private boolean requestFocusInternal() { @@ -360,6 +370,20 @@ private boolean requestFocusInternal() { if (getShowSoftInputOnFocus()) { showSoftKeyboard(); } + + return focused; + } + + // For cases like autoFocus, or ref.focus() where we request focus programatically and not through + // interacting with the EditText directly (like clicking on it). We cannot use stock + // requestFocus() because it will not pop up the soft keyboard, only clicking the input will do + // that. This method will eventually replace requestFocusInternal() + private boolean requestFocusProgramatically() { + boolean focused = super.requestFocus(View.FOCUS_DOWN, null); + if (isInTouchMode() && getShowSoftInputOnFocus()) { + showSoftKeyboard(); + } + return focused; } @@ -632,7 +656,11 @@ public void maybeUpdateTypeface() { // VisibleForTesting from {@link TextInputEventsTestCase}. public void requestFocusFromJS() { - requestFocusInternal(); + if (ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior()) { + requestFocusProgramatically(); + } else { + requestFocusInternal(); + } } /* package */ void clearFocusFromJS() { @@ -1079,7 +1107,11 @@ public void onAttachedToWindow() { } if (mAutoFocus && !mDidAttachToWindow) { - requestFocusInternal(); + if (ReactNativeFeatureFlags.useEditTextStockAndroidFocusBehavior()) { + requestFocusProgramatically(); + } else { + requestFocusInternal(); + } } mDidAttachToWindow = true; diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index d3d0dc3ea1e040..97e7af5ae37ade 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7c3853858da56eb5f471abccf9dcbf55>> + * @generated SignedSource<<63ebe186ccd5ee30fc654aa8d90f27f9>> */ /** @@ -255,6 +255,12 @@ class ReactNativeFeatureFlagsProviderHolder return method(javaProvider_); } + bool useEditTextStockAndroidFocusBehavior() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useEditTextStockAndroidFocusBehavior"); + return method(javaProvider_); + } + bool useFabricInterop() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useFabricInterop"); @@ -493,6 +499,11 @@ bool JReactNativeFeatureFlagsCxxInterop::useAlwaysAvailableJSErrorHandling( return ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling(); } +bool JReactNativeFeatureFlagsCxxInterop::useEditTextStockAndroidFocusBehavior( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::useEditTextStockAndroidFocusBehavior(); +} + bool JReactNativeFeatureFlagsCxxInterop::useFabricInterop( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::useFabricInterop(); @@ -677,6 +688,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "useAlwaysAvailableJSErrorHandling", JReactNativeFeatureFlagsCxxInterop::useAlwaysAvailableJSErrorHandling), + makeNativeMethod( + "useEditTextStockAndroidFocusBehavior", + JReactNativeFeatureFlagsCxxInterop::useEditTextStockAndroidFocusBehavior), makeNativeMethod( "useFabricInterop", JReactNativeFeatureFlagsCxxInterop::useFabricInterop), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 80ef331dff8352..f8631b2e947bc4 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<77b4ed5aa33290ba9da1719544e974cb>> + * @generated SignedSource<> */ /** @@ -138,6 +138,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool useAlwaysAvailableJSErrorHandling( facebook::jni::alias_ref); + static bool useEditTextStockAndroidFocusBehavior( + facebook::jni::alias_ref); + static bool useFabricInterop( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt index 91aabb17f3b80a..43de322a28c6f5 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt @@ -25,6 +25,7 @@ import com.facebook.react.bridge.BridgeReactContext import com.facebook.react.bridge.CatalystInstance import com.facebook.react.bridge.JavaOnlyMap import com.facebook.react.bridge.ReactTestHelper.createMockCatalystInstance +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests import com.facebook.react.uimanager.DisplayMetricsHolder import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.ThemedReactContext @@ -65,6 +66,7 @@ class ReactTextInputPropertyTest { manager = ReactTextInputManager() DisplayMetricsHolder.setWindowDisplayMetrics(DisplayMetrics()) view = manager.createViewInstance(themedContext) + ReactNativeFeatureFlagsForTests.setUp() } @Test diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index f71795970ae0c7..3a27b0604c156c 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7d301656072183649246db8fa738fc4d>> + * @generated SignedSource<<58c69846193598ce0c3666c37b8a185f>> */ /** @@ -170,6 +170,10 @@ bool ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling() { return getAccessor().useAlwaysAvailableJSErrorHandling(); } +bool ReactNativeFeatureFlags::useEditTextStockAndroidFocusBehavior() { + return getAccessor().useEditTextStockAndroidFocusBehavior(); +} + bool ReactNativeFeatureFlags::useFabricInterop() { return getAccessor().useFabricInterop(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 4d075604aa7da4..68b5c50f09744b 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<1d578508c3cd69bbf9616a811508a03e>> */ /** @@ -219,6 +219,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool useAlwaysAvailableJSErrorHandling(); + /** + * If true, focusing in ReactEditText will mainly use stock Android requestFocus() behavior. If false it will use legacy custom focus behavior. + */ + RN_EXPORT static bool useEditTextStockAndroidFocusBehavior(); + /** * Should this application enable the Fabric Interop Layer for Android? If yes, the application will behave so that it can accept non-Fabric components and render them on Fabric. This toggle is controlling extra logic such as custom event dispatching that are needed for the Fabric Interop Layer to work correctly. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 4e528eff110880..8d1253b74d1bee 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<42e2ae5cbfb15c17a7d60a771128858a>> */ /** @@ -677,6 +677,24 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::useEditTextStockAndroidFocusBehavior() { + auto flagValue = useEditTextStockAndroidFocusBehavior_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(36, "useEditTextStockAndroidFocusBehavior"); + + flagValue = currentProvider_->useEditTextStockAndroidFocusBehavior(); + useEditTextStockAndroidFocusBehavior_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { auto flagValue = useFabricInterop_.load(); @@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(36, "useFabricInterop"); + markFlagAsAccessed(37, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -704,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::useImmediateExecutorInAndroidBridgeless() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(37, "useImmediateExecutorInAndroidBridgeless"); + markFlagAsAccessed(38, "useImmediateExecutorInAndroidBridgeless"); flagValue = currentProvider_->useImmediateExecutorInAndroidBridgeless(); useImmediateExecutorInAndroidBridgeless_ = flagValue; @@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(38, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(39, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimisedViewPreallocationOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(39, "useOptimisedViewPreallocationOnAndroid"); + markFlagAsAccessed(40, "useOptimisedViewPreallocationOnAndroid"); flagValue = currentProvider_->useOptimisedViewPreallocationOnAndroid(); useOptimisedViewPreallocationOnAndroid_ = flagValue; @@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(40, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(41, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(41, "useRawPropsJsiValue"); + markFlagAsAccessed(42, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::useRuntimeShadowNodeReferenceUpdate() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(42, "useRuntimeShadowNodeReferenceUpdate"); + markFlagAsAccessed(43, "useRuntimeShadowNodeReferenceUpdate"); flagValue = currentProvider_->useRuntimeShadowNodeReferenceUpdate(); useRuntimeShadowNodeReferenceUpdate_ = flagValue; @@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(43, "useTurboModuleInterop"); + markFlagAsAccessed(44, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(44, "useTurboModules"); + markFlagAsAccessed(45, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index ac0c3e53a1dbd7..3c0468e8e04fa0 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<8eb2b5d6dd367826ff7bc899afbdea60>> + * @generated SignedSource<<8423497c73f5315fdc3e8b4fa0bdc8c2>> */ /** @@ -68,6 +68,7 @@ class ReactNativeFeatureFlagsAccessor { bool loadVectorDrawablesOnImages(); bool traceTurboModulePromiseRejectionsOnAndroid(); bool useAlwaysAvailableJSErrorHandling(); + bool useEditTextStockAndroidFocusBehavior(); bool useFabricInterop(); bool useImmediateExecutorInAndroidBridgeless(); bool useNativeViewConfigsInBridgelessMode(); @@ -88,7 +89,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 45> accessedFeatureFlags_; + std::array, 46> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> completeReactInstanceCreationOnBgThreadOnAndroid_; @@ -126,6 +127,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> loadVectorDrawablesOnImages_; std::atomic> traceTurboModulePromiseRejectionsOnAndroid_; std::atomic> useAlwaysAvailableJSErrorHandling_; + std::atomic> useEditTextStockAndroidFocusBehavior_; std::atomic> useFabricInterop_; std::atomic> useImmediateExecutorInAndroidBridgeless_; std::atomic> useNativeViewConfigsInBridgelessMode_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index aec774f80d20dc..8f0a2fabb42aac 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -171,6 +171,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool useEditTextStockAndroidFocusBehavior() override { + return true; + } + bool useFabricInterop() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 5a6b8a82ae33f2..7fd97a9b561519 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<179524938d2e6148ef87835844c4135e>> */ /** @@ -369,6 +369,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::useAlwaysAvailableJSErrorHandling(); } + bool useEditTextStockAndroidFocusBehavior() override { + auto value = values_["useEditTextStockAndroidFocusBehavior"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::useEditTextStockAndroidFocusBehavior(); + } + bool useFabricInterop() override { auto value = values_["useFabricInterop"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index c89d65c0d88b54..9975fc02014384 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<36bfe0037e4dba3b6eb6e95075914fce>> + * @generated SignedSource<> */ /** @@ -61,6 +61,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool loadVectorDrawablesOnImages() = 0; virtual bool traceTurboModulePromiseRejectionsOnAndroid() = 0; virtual bool useAlwaysAvailableJSErrorHandling() = 0; + virtual bool useEditTextStockAndroidFocusBehavior() = 0; virtual bool useFabricInterop() = 0; virtual bool useImmediateExecutorInAndroidBridgeless() = 0; virtual bool useNativeViewConfigsInBridgelessMode() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index f326fcd073b5f8..f8108b98f97470 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<81f3e20643ddb2612eaa9276bb75e70e>> */ /** @@ -224,6 +224,11 @@ bool NativeReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling( return ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling(); } +bool NativeReactNativeFeatureFlags::useEditTextStockAndroidFocusBehavior( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::useEditTextStockAndroidFocusBehavior(); +} + bool NativeReactNativeFeatureFlags::useFabricInterop( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::useFabricInterop(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 7f4364d355d522..b96b81b87853ef 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<0852372c6b328f128319949a01aa8787>> */ /** @@ -109,6 +109,8 @@ class NativeReactNativeFeatureFlags bool useAlwaysAvailableJSErrorHandling(jsi::Runtime& runtime); + bool useEditTextStockAndroidFocusBehavior(jsi::Runtime& runtime); + bool useFabricInterop(jsi::Runtime& runtime); bool useImmediateExecutorInAndroidBridgeless(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 9be0b03e20a2ab..2248bae913f5cb 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -390,6 +390,15 @@ const definitions: FeatureFlagDefinitions = { purpose: 'release', }, }, + useEditTextStockAndroidFocusBehavior: { + defaultValue: true, + metadata: { + description: + 'If true, focusing in ReactEditText will mainly use stock Android requestFocus() behavior. If false it will use legacy custom focus behavior.', + expectedReleaseValue: true, + purpose: 'release', + }, + }, useFabricInterop: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 9e3e918b870ee3..b97da614d309b6 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3bdec862f75745ce81e9cf23fef82468>> + * @generated SignedSource<> * @flow strict */ @@ -87,6 +87,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ loadVectorDrawablesOnImages: Getter, traceTurboModulePromiseRejectionsOnAndroid: Getter, useAlwaysAvailableJSErrorHandling: Getter, + useEditTextStockAndroidFocusBehavior: Getter, useFabricInterop: Getter, useImmediateExecutorInAndroidBridgeless: Getter, useNativeViewConfigsInBridgelessMode: Getter, @@ -331,6 +332,10 @@ export const traceTurboModulePromiseRejectionsOnAndroid: Getter = creat * In Bridgeless mode, use the always available javascript error reporting pipeline. */ export const useAlwaysAvailableJSErrorHandling: Getter = createNativeFlagGetter('useAlwaysAvailableJSErrorHandling', false); +/** + * If true, focusing in ReactEditText will mainly use stock Android requestFocus() behavior. If false it will use legacy custom focus behavior. + */ +export const useEditTextStockAndroidFocusBehavior: Getter = createNativeFlagGetter('useEditTextStockAndroidFocusBehavior', true); /** * Should this application enable the Fabric Interop Layer for Android? If yes, the application will behave so that it can accept non-Fabric components and render them on Fabric. This toggle is controlling extra logic such as custom event dispatching that are needed for the Fabric Interop Layer to work correctly. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index f9f4a5bc187baf..5a0c476acbb924 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<37e95652ef5d824bb05e78ebdb051e43>> + * @generated SignedSource<<9fd249e6c81d4aaa9793c003d986524c>> * @flow strict */ @@ -60,6 +60,7 @@ export interface Spec extends TurboModule { +loadVectorDrawablesOnImages?: () => boolean; +traceTurboModulePromiseRejectionsOnAndroid?: () => boolean; +useAlwaysAvailableJSErrorHandling?: () => boolean; + +useEditTextStockAndroidFocusBehavior?: () => boolean; +useFabricInterop?: () => boolean; +useImmediateExecutorInAndroidBridgeless?: () => boolean; +useNativeViewConfigsInBridgelessMode?: () => boolean;