Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.core.view.ViewCompat.FocusDirection;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
Expand Down Expand Up @@ -65,6 +66,7 @@
import com.facebook.react.fabric.mounting.mountitems.MountItem;
import com.facebook.react.fabric.mounting.mountitems.MountItemFactory;
import com.facebook.react.fabric.mounting.mountitems.PrefetchResourcesMountItem;
import com.facebook.react.fabric.mounting.mountitems.PullTransactionMountItem;
import com.facebook.react.fabric.mounting.mountitems.SynchronousMountItem;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags;
Expand Down Expand Up @@ -934,7 +936,8 @@ private void scheduleMountItem(
long layoutEndTime,
long finishTransactionStartTime,
long finishTransactionEndTime,
int affectedLayoutNodesCount) {
int affectedLayoutNodesCount,
boolean synchronous) {
// When Binding.cpp calls scheduleMountItems during a commit phase, it always calls with
// a BatchMountItem. No other sites call into this with a BatchMountItem, and Binding.cpp only
// calls scheduleMountItems with a BatchMountItem.
Expand All @@ -948,8 +951,9 @@ private void scheduleMountItem(
} else {
shouldSchedule = mountItem != null;
}
// In case of sync rendering, this could be called on the UI thread. Otherwise,
// it should ~always be called on the JS thread.
// In the push model, this is ~always called on the JS thread, or on the UI
// thread in case of sync rendering.
// In the pull model, this is always called on the UI thread, at pull time.
for (UIManagerListener listener : mListeners) {
listener.didScheduleMountItems(this);
}
Expand All @@ -964,16 +968,22 @@ private void scheduleMountItem(

if (shouldSchedule) {
Assertions.assertNotNull(mountItem, "MountItem is null");
mMountItemDispatcher.addMountItem(mountItem);
if (UiThreadUtil.isOnUiThread()) {
Runnable runnable =
new GuardedRunnable(mReactApplicationContext) {
@Override
public void runGuarded() {
mMountItemDispatcher.tryDispatchMountItems();
}
};
runnable.run();
if (synchronous) {
// Pull model: we are already on the UI thread, inside the dispatcher's loop executing
// a PullTransactionMountItem. We don't schedule the item, we execute it directly.
mountItem.execute(mMountingManager);
} else {
mMountItemDispatcher.addMountItem(mountItem);
if (UiThreadUtil.isOnUiThread()) {
Runnable runnable =
new GuardedRunnable(mReactApplicationContext) {
@Override
public void runGuarded() {
mMountItemDispatcher.tryDispatchMountItems();
}
};
runnable.run();
}
}
}

Expand Down Expand Up @@ -1009,6 +1019,26 @@ public void runGuarded() {
}
}

/**
* Pull model: called from C++ via JNI (usually on the commit thread) to signal that a transaction
* is available for {@code surfaceId}. Enqueues a PullTransactionMountItem so the UI thread pulls
* and applies the transaction itself, preserving mount-item ordering.
*/
@SuppressWarnings("unused")
@AnyThread
@ThreadConfined(ANY)
@VisibleForTesting
void onTransactionAvailable(int surfaceId) {
FabricUIManagerBinding binding = mBinding;
if (binding == null) {
return;
}
mMountItemDispatcher.addMountItem(new PullTransactionMountItem(surfaceId, binding));
if (UiThreadUtil.isOnUiThread()) {
mMountItemDispatcher.tryDispatchMountItems();
}
}

@SuppressWarnings("unused")
@AnyThread
@ThreadConfined(ANY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ internal class FabricUIManagerBinding : HybridClassBase() {

external fun reportMount(surfaceId: Int)

external fun pullAndExecuteTransaction(surfaceId: Int)

external fun mergeReactRevision(surfaceId: Int)

fun register(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react.fabric.mounting.mountitems

import com.facebook.proguard.annotations.DoNotStripAny
import com.facebook.react.fabric.FabricUIManagerBinding
import com.facebook.react.fabric.mounting.MountingManager

/**
* Pull model mount item. Enqueued (on the commit thread) when C++ notifies that a transaction is
* available for a surface. When it executes on the UI thread it asks C++ to pull the surface's
* pending transaction and apply it synchronously, so the diff + batch construction happens on the
* UI thread instead of the commit thread (matching iOS).
*/
@DoNotStripAny
internal class PullTransactionMountItem(
private val surfaceId: Int,
private val binding: FabricUIManagerBinding,
) : MountItem {

override fun execute(mountingManager: MountingManager) {
binding.pullAndExecuteTransaction(surfaceId)
}

override fun getSurfaceId(): Int = surfaceId

override fun toString(): String = "PullTransactionMountItem [surfaceId: $surfaceId]"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<<c5ce707ba940ef24ca6c261fd9b2baf7>>
* @generated SignedSource<<9117484347f2ff31f7ba6e5a2ac38d93>>
*/

/**
Expand Down Expand Up @@ -204,6 +204,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableImagePrefetchingAndroid(): Boolean = accessor.enableImagePrefetchingAndroid()

/**
* When enabled, Image `tintColor` is handled as a true optional: any defined color is applied as a tint — including `transparent` (alpha 0), which renders the image invisible — and an unset value clears a previously applied tint. When disabled, the prior behavior is preserved, where a transparent `tintColor` is treated as unassigned and the image renders untinted.
*/
@JvmStatic
public fun enableImageTransparentTintColor(): Boolean = accessor.enableImageTransparentTintColor()

/**
* Dispatches state updates for content offset changes synchronously on the main thread.
*/
Expand Down Expand Up @@ -252,6 +258,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableModuleArgumentNSNullConversionIOS(): Boolean = accessor.enableModuleArgumentNSNullConversionIOS()

/**
* When enabled, Android mounts transactions with the pull model (like iOS): the commit thread no longer pulls and builds the mount batch in schedulerShouldRenderTransactions. Instead the UI thread pulls the transaction itself via a PullTransactionMountItem enqueued in the MountItemDispatcher, builds the IntBufferBatchMountItem, and applies it synchronously. Implies rawProps accumulation (enableAccumulatedUpdatesInRawPropsAndroid behavior).
*/
@JvmStatic
public fun enableMountingCoordinatorPullModelAndroid(): Boolean = accessor.enableMountingCoordinatorPullModelAndroid()

/**
* Enables the MutationObserver Web API in React Native.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<<628ede95c745280c1ceaa73c6dc45de0>>
* @generated SignedSource<<a506d2515fce404e19bd61099cc60117>>
*/

/**
Expand Down Expand Up @@ -49,6 +49,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableImagePrefetchingAndroidCache: Boolean? = null
private var enableImageTransparentTintColorCache: Boolean? = null
private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null
private var enableImperativeFocusCache: Boolean? = null
private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null
Expand All @@ -57,6 +58,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
private var enableModuleArgumentNSNullConversionIOSCache: Boolean? = null
private var enableMountingCoordinatorPullModelAndroidCache: Boolean? = null
private var enableMutationObserverByDefaultCache: Boolean? = null
private var enableNativeCSSParsingCache: Boolean? = null
private var enablePreparedTextLayoutCache: Boolean? = null
Expand Down Expand Up @@ -367,6 +369,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}

override fun enableImageTransparentTintColor(): Boolean {
var cached = enableImageTransparentTintColorCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableImageTransparentTintColor()
enableImageTransparentTintColorCache = cached
}
return cached
}

override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean {
var cached = enableImmediateUpdateModeForContentOffsetChangesCache
if (cached == null) {
Expand Down Expand Up @@ -439,6 +450,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}

override fun enableMountingCoordinatorPullModelAndroid(): Boolean {
var cached = enableMountingCoordinatorPullModelAndroidCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableMountingCoordinatorPullModelAndroid()
enableMountingCoordinatorPullModelAndroidCache = cached
}
return cached
}

override fun enableMutationObserverByDefault(): Boolean {
var cached = enableMutationObserverByDefaultCache
if (cached == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<<6a0e60fe118f3651dc76c895a37fbce2>>
* @generated SignedSource<<1ef72233f02973021b83bd2e2aa1f69b>>
*/

/**
Expand Down Expand Up @@ -86,6 +86,8 @@ public object ReactNativeFeatureFlagsCxxInterop {

@DoNotStrip @JvmStatic public external fun enableImagePrefetchingAndroid(): Boolean

@DoNotStrip @JvmStatic public external fun enableImageTransparentTintColor(): Boolean

@DoNotStrip @JvmStatic public external fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean

@DoNotStrip @JvmStatic public external fun enableImperativeFocus(): Boolean
Expand All @@ -102,6 +104,8 @@ public object ReactNativeFeatureFlagsCxxInterop {

@DoNotStrip @JvmStatic public external fun enableModuleArgumentNSNullConversionIOS(): Boolean

@DoNotStrip @JvmStatic public external fun enableMountingCoordinatorPullModelAndroid(): Boolean

@DoNotStrip @JvmStatic public external fun enableMutationObserverByDefault(): Boolean

@DoNotStrip @JvmStatic public external fun enableNativeCSSParsing(): Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<<37e483d52d12a735e646c8ac06acb62c>>
* @generated SignedSource<<eb9958ddc04dd1cd8d1add366f5f7741>>
*/

/**
Expand Down Expand Up @@ -81,6 +81,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi

override fun enableImagePrefetchingAndroid(): Boolean = false

override fun enableImageTransparentTintColor(): Boolean = false

override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean = false

override fun enableImperativeFocus(): Boolean = false
Expand All @@ -97,6 +99,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi

override fun enableModuleArgumentNSNullConversionIOS(): Boolean = false

override fun enableMountingCoordinatorPullModelAndroid(): Boolean = false

override fun enableMutationObserverByDefault(): Boolean = false

override fun enableNativeCSSParsing(): Boolean = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<<5b7a6ca47ca43f473596e35dfced16e0>>
* @generated SignedSource<<9ae32c46a5a6310ef96eb91c9ea5b12e>>
*/

/**
Expand Down Expand Up @@ -53,6 +53,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableImagePrefetchingAndroidCache: Boolean? = null
private var enableImageTransparentTintColorCache: Boolean? = null
private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null
private var enableImperativeFocusCache: Boolean? = null
private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null
Expand All @@ -61,6 +62,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
private var enableModuleArgumentNSNullConversionIOSCache: Boolean? = null
private var enableMountingCoordinatorPullModelAndroidCache: Boolean? = null
private var enableMutationObserverByDefaultCache: Boolean? = null
private var enableNativeCSSParsingCache: Boolean? = null
private var enablePreparedTextLayoutCache: Boolean? = null
Expand Down Expand Up @@ -400,6 +402,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}

override fun enableImageTransparentTintColor(): Boolean {
var cached = enableImageTransparentTintColorCache
if (cached == null) {
cached = currentProvider.enableImageTransparentTintColor()
accessedFeatureFlags.add("enableImageTransparentTintColor")
enableImageTransparentTintColorCache = cached
}
return cached
}

override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean {
var cached = enableImmediateUpdateModeForContentOffsetChangesCache
if (cached == null) {
Expand Down Expand Up @@ -480,6 +492,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}

override fun enableMountingCoordinatorPullModelAndroid(): Boolean {
var cached = enableMountingCoordinatorPullModelAndroidCache
if (cached == null) {
cached = currentProvider.enableMountingCoordinatorPullModelAndroid()
accessedFeatureFlags.add("enableMountingCoordinatorPullModelAndroid")
enableMountingCoordinatorPullModelAndroidCache = cached
}
return cached
}

override fun enableMutationObserverByDefault(): Boolean {
var cached = enableMutationObserverByDefaultCache
if (cached == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<<ee254c53ae1b4bc03045926028e898e5>>
* @generated SignedSource<<1a1d47f2d85404c776e55db40f7dbc6e>>
*/

/**
Expand Down Expand Up @@ -81,6 +81,8 @@ public interface ReactNativeFeatureFlagsProvider {

@DoNotStrip public fun enableImagePrefetchingAndroid(): Boolean

@DoNotStrip public fun enableImageTransparentTintColor(): Boolean

@DoNotStrip public fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean

@DoNotStrip public fun enableImperativeFocus(): Boolean
Expand All @@ -97,6 +99,8 @@ public interface ReactNativeFeatureFlagsProvider {

@DoNotStrip public fun enableModuleArgumentNSNullConversionIOS(): Boolean

@DoNotStrip public fun enableMountingCoordinatorPullModelAndroid(): Boolean

@DoNotStrip public fun enableMutationObserverByDefault(): Boolean

@DoNotStrip public fun enableNativeCSSParsing(): Boolean
Expand Down
Loading
Loading