Use mapbuffer for ReactViewGroup

Summary:
Provides an alternative way of diffing props on Android side. Instead of passing dynamic JSON values from JS, the new approach diff C++ props and serializes difference into a `MapBuffer`. Current implementation does this only for `RCTView` component to test performance, correctness and memory impact (as MapBuffers are supposed to be more compact and performant).

This way of passing props allows for additional alignment between platforms, as C++ props are now source of truth for the view state, similar to iOS. At the same time, changing serialization method requires reimplementing `ViewManager` codegen (done manually for now) to account for `MapBuffer`s.

Changelog: [Added][Android] - Added an experimental prop serialization path based on MapBuffer

Reviewed By: mdvacca

Differential Revision: D33735245

fbshipit-source-id: 1515b5fe92f6557ae55abe215ce48277c83974a6
This commit is contained in:
Andrei Shikov
2022-02-22 11:41:39 -08:00
committed by Facebook GitHub Bot
parent b1a779392d
commit cbcdaae2b5
14 changed files with 777 additions and 68 deletions
@@ -31,6 +31,10 @@ public class JavaOnlyMap implements ReadableMap, WritableMap {
return new JavaOnlyMap(keysAndValues);
}
public static JavaOnlyMap from(Map<String, Object> map) {
return new JavaOnlyMap(map);
}
public static JavaOnlyMap deepClone(ReadableMap map) {
JavaOnlyMap res = new JavaOnlyMap();
ReadableMapKeySetIterator iter = map.keySetIterator();
@@ -301,6 +301,36 @@ public class ReadableMapBuffer implements Iterable<ReadableMapBuffer.MapBufferEn
return thisByteBuffer.equals(otherByteBuffer);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("{");
for (MapBufferEntry entry : this) {
int key = entry.getKey();
builder.append(key);
builder.append('=');
switch (entry.getType()) {
case BOOL:
builder.append(entry.getBoolean());
break;
case INT:
builder.append(entry.getInt());
break;
case DOUBLE:
builder.append(entry.getDouble());
break;
case STRING:
builder.append(entry.getString());
break;
case MAP:
builder.append(entry.getReadableMapBuffer().toString());
break;
}
builder.append(',');
}
builder.append('}');
return builder.toString();
}
/** @return an {@link Iterator<MapBufferEntry>} for the entries of this MapBuffer. */
@Override
public Iterator<MapBufferEntry> iterator() {
@@ -372,7 +402,7 @@ public class ReadableMapBuffer implements Iterable<ReadableMapBuffer.MapBufferEn
}
/** @return the String value that is stored in this {@link MapBufferEntry}. */
public @Nullable String getString() {
public String getString() {
assertType(DataType.STRING);
return readStringValue(mBucketOffset + VALUE_OFFSET);
}
@@ -380,7 +410,7 @@ public class ReadableMapBuffer implements Iterable<ReadableMapBuffer.MapBufferEn
/**
* @return the {@link ReadableMapBuffer} value that is stored in this {@link MapBufferEntry}.
*/
public @Nullable ReadableMapBuffer getReadableMapBuffer() {
public ReadableMapBuffer getReadableMapBuffer() {
assertType(DataType.MAP);
return readMapBufferValue(mBucketOffset + VALUE_OFFSET);
}
@@ -704,7 +704,7 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
int rootTag,
int reactTag,
final String componentName,
@Nullable ReadableMap props,
@Nullable Object props,
@Nullable Object stateWrapper,
@Nullable Object eventEmitterWrapper,
boolean isLayoutable) {
@@ -28,8 +28,11 @@ CppMountItem CppMountItem::RemoveMountItem(
int index) {
return {CppMountItem::Type::Remove, parentView, shadowView, {}, index};
}
CppMountItem CppMountItem::UpdatePropsMountItem(ShadowView const &shadowView) {
return {CppMountItem::Type::UpdateProps, {}, {}, shadowView, -1};
CppMountItem CppMountItem::UpdatePropsMountItem(
ShadowView const &oldShadowView,
ShadowView const &newShadowView) {
return {
CppMountItem::Type::UpdateProps, {}, oldShadowView, newShadowView, -1};
}
CppMountItem CppMountItem::UpdateStateMountItem(ShadowView const &shadowView) {
return {CppMountItem::Type::UpdateState, {}, {}, shadowView, -1};
@@ -35,7 +35,9 @@ struct CppMountItem final {
ShadowView const &shadowView,
int index);
static CppMountItem UpdatePropsMountItem(ShadowView const &shadowView);
static CppMountItem UpdatePropsMountItem(
ShadowView const &oldShadowView,
ShadowView const &newShadowView);
static CppMountItem UpdateStateMountItem(ShadowView const &shadowView);
@@ -8,6 +8,7 @@
#include "FabricMountingManager.h"
#include "EventEmitterWrapper.h"
#include "StateWrapperImpl.h"
#include "viewPropConversions.h"
#include <react/jni/ReadableNativeMap.h>
#include <react/renderer/components/scrollview/ScrollViewProps.h>
@@ -177,11 +178,6 @@ static inline void writeIntBufferTypePreamble(
}
}
inline local_ref<ReadableMap::javaobject> castReadableMap(
local_ref<ReadableNativeMap::javaobject> const &nativeMap) {
return make_local(reinterpret_cast<ReadableMap::javaobject>(nativeMap.get()));
}
inline local_ref<ReadableArray::javaobject> castReadableArray(
local_ref<ReadableNativeArray::javaobject> const &nativeArray) {
return make_local(
@@ -222,6 +218,22 @@ static inline float scale(Float value, Float pointScaleFactor) {
return result;
}
local_ref<jobject> FabricMountingManager::getProps(
ShadowView const &oldShadowView,
ShadowView const &newShadowView) {
if (useMapBufferForViewProps_ &&
newShadowView.traits.check(ShadowNodeTraits::Trait::View)) {
auto oldProps = oldShadowView.props != nullptr
? static_cast<ViewProps const &>(*oldShadowView.props)
: ViewProps{};
auto newProps = static_cast<ViewProps const &>(*newShadowView.props);
return ReadableMapBuffer::createWithContents(
viewPropsDiff(oldProps, newProps));
} else {
return ReadableNativeMap::newObjectCxxArgs(newShadowView.props->rawProps);
}
}
void FabricMountingManager::executeMount(
MountingCoordinator::Shared const &mountingCoordinator) {
std::lock_guard<std::recursive_mutex> lock(commitMutex_);
@@ -308,7 +320,8 @@ void FabricMountingManager::executeMount(
if (!isVirtual) {
if (oldChildShadowView.props != newChildShadowView.props) {
cppUpdatePropsMountItems.push_back(
CppMountItem::UpdatePropsMountItem(newChildShadowView));
CppMountItem::UpdatePropsMountItem(
oldChildShadowView, newChildShadowView));
}
if (oldChildShadowView.state != newChildShadowView.state) {
cppUpdateStateMountItems.push_back(
@@ -367,7 +380,7 @@ void FabricMountingManager::executeMount(
shouldRememberAllocatedViews_ ? allocationCheck : revisionCheck;
if (shouldCreateView) {
cppUpdatePropsMountItems.push_back(
CppMountItem::UpdatePropsMountItem(newChildShadowView));
CppMountItem::UpdatePropsMountItem({}, newChildShadowView));
}
// State
@@ -484,7 +497,6 @@ void FabricMountingManager::executeMount(
// Allocate the intBuffer and object array, now that we know exact sizes
// necessary
// TODO: don't allocate at all if size is zero
jintArray intBufferArray = env->NewIntArray(batchMountItemIntsSize);
local_ref<JArrayClass<jobject>> objBufferArray =
JArrayClass<jobject>::newArray(batchMountItemObjectsSize);
@@ -525,10 +537,8 @@ void FabricMountingManager::executeMount(
int isLayoutable =
mountItem.newChildShadowView.layoutMetrics != EmptyLayoutMetrics ? 1
: 0;
local_ref<ReadableMap::javaobject> props =
castReadableMap(ReadableNativeMap::newObjectCxxArgs(
mountItem.newChildShadowView.props->rawProps));
local_ref<JObject> props =
getProps(mountItem.oldChildShadowView, mountItem.newChildShadowView);
// Do not hold onto Java object from C
// We DO want to hold onto C object from Java, since we don't know the
@@ -584,11 +594,8 @@ void FabricMountingManager::executeMount(
temp[0] = mountItem.newChildShadowView.tag;
env->SetIntArrayRegion(intBufferArray, intBufferPosition, 1, temp);
intBufferPosition += 1;
auto newProps = mountItem.newChildShadowView.props->rawProps;
local_ref<ReadableMap::javaobject> newPropsReadableMap =
castReadableMap(ReadableNativeMap::newObjectCxxArgs(newProps));
(*objBufferArray)[objBufferPosition++] = newPropsReadableMap.get();
(*objBufferArray)[objBufferPosition++] =
getProps(mountItem.oldChildShadowView, mountItem.newChildShadowView);
}
}
if (!cppUpdateStateMountItems.empty()) {
@@ -795,15 +802,11 @@ void FabricMountingManager::preallocateShadowView(
bool isLayoutableShadowNode = shadowView.layoutMetrics != EmptyLayoutMetrics;
static auto preallocateView = jni::findClassStatic(UIManagerJavaDescriptor)
->getMethod<void(
jint,
jint,
jstring,
ReadableMap::javaobject,
jobject,
jobject,
jboolean)>("preallocateView");
static auto preallocateView =
jni::findClassStatic(UIManagerJavaDescriptor)
->getMethod<void(
jint, jint, jstring, jobject, jobject, jobject, jboolean)>(
"preallocateView");
// Do not hold onto Java object from C
// We DO want to hold onto C object from Java, since we don't know the
@@ -826,8 +829,8 @@ void FabricMountingManager::preallocateShadowView(
}
}
local_ref<ReadableMap::javaobject> props = castReadableMap(
ReadableNativeMap::newObjectCxxArgs(shadowView.props->rawProps));
local_ref<JObject> props = getProps({}, shadowView);
auto component = getPlatformComponentName(shadowView);
preallocateView(
@@ -942,6 +945,8 @@ FabricMountingManager::FabricMountingManager(
useOverflowInset_ = doesUseOverflowInset();
shouldRememberAllocatedViews_ = config->getBool(
"react_native_new_architecture:remember_views_on_mount_android");
useMapBufferForViewProps_ = config->getBool(
"react_native_new_architecture:use_mapbuffer_for_viewprops");
}
} // namespace react
@@ -76,6 +76,11 @@ class FabricMountingManager {
bool disableRevisionCheckForPreallocation_{false};
bool useOverflowInset_{false};
bool shouldRememberAllocatedViews_{false};
bool useMapBufferForViewProps_{false};
jni::local_ref<jobject> getProps(
ShadowView const &oldShadowView,
ShadowView const &newShadowView);
};
} // namespace react
@@ -26,6 +26,7 @@ import com.facebook.react.bridge.RetryableMountingLayerException;
import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.common.mapbuffer.ReadableMapBuffer;
import com.facebook.react.config.ReactFeatureFlags;
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.MountingManager.MountItemExecutor;
@@ -42,6 +43,8 @@ import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.ViewManagerRegistry;
import com.facebook.react.views.view.ReactMapBufferViewManager;
import com.facebook.react.views.view.ReactViewManagerWrapper;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -163,7 +166,13 @@ public class SurfaceMountingManager {
return;
}
mTagToViewState.put(mSurfaceId, new ViewState(mSurfaceId, rootView, mRootViewManager, true));
mTagToViewState.put(
mSurfaceId,
new ViewState(
mSurfaceId,
rootView,
new ReactViewManagerWrapper.DefaultViewManager((ViewManager) mRootViewManager),
true));
Runnable runnable =
new Runnable() {
@@ -534,7 +543,7 @@ public class SurfaceMountingManager {
public void createView(
@NonNull String componentName,
int reactTag,
@Nullable ReadableMap props,
@Nullable Object props,
@Nullable StateWrapper stateWrapper,
@Nullable EventEmitterWrapper eventEmitterWrapper,
boolean isLayoutable) {
@@ -572,41 +581,48 @@ public class SurfaceMountingManager {
public void createViewUnsafe(
@NonNull String componentName,
int reactTag,
@Nullable ReadableMap props,
@Nullable Object props,
@Nullable StateWrapper stateWrapper,
@Nullable EventEmitterWrapper eventEmitterWrapper,
boolean isLayoutable) {
View view = null;
ViewManager viewManager = null;
ReactViewManagerWrapper viewManager = null;
ReactStylesDiffMap propsDiffMap = null;
if (props != null) {
propsDiffMap = new ReactStylesDiffMap(props);
Object propMap;
if (props instanceof ReadableMap) {
propMap = new ReactStylesDiffMap((ReadableMap) props);
} else {
propMap = props;
}
if (isLayoutable) {
viewManager = mViewManagerRegistry.get(componentName);
viewManager =
props instanceof ReadableMapBuffer
? ReactMapBufferViewManager.INSTANCE
: new ReactViewManagerWrapper.DefaultViewManager(
mViewManagerRegistry.get(componentName));
// View Managers are responsible for dealing with initial state and props.
view =
viewManager.createView(
reactTag, mThemedReactContext, propsDiffMap, stateWrapper, mJSResponderHandler);
reactTag, mThemedReactContext, propMap, stateWrapper, mJSResponderHandler);
}
ViewState viewState = new ViewState(reactTag, view, viewManager);
viewState.mCurrentProps = propsDiffMap;
viewState.mCurrentProps = propMap;
viewState.mStateWrapper = stateWrapper;
viewState.mEventEmitter = eventEmitterWrapper;
mTagToViewState.put(reactTag, viewState);
}
public void updateProps(int reactTag, ReadableMap props) {
public void updateProps(int reactTag, Object props) {
if (isStopped()) {
return;
}
ViewState viewState = getViewState(reactTag);
viewState.mCurrentProps = new ReactStylesDiffMap(props);
viewState.mCurrentProps =
props instanceof ReadableMap ? new ReactStylesDiffMap((ReadableMap) props) : props;
View view = viewState.mView;
if (view == null) {
@@ -751,7 +767,7 @@ public class SurfaceMountingManager {
throw new IllegalStateException("Unable to find View for tag: " + reactTag);
}
ViewManager viewManager = viewState.mViewManager;
ReactViewManagerWrapper viewManager = viewState.mViewManager;
if (viewManager == null) {
throw new IllegalStateException("Unable to find ViewManager for view: " + viewState);
}
@@ -801,7 +817,7 @@ public class SurfaceMountingManager {
StateWrapper prevStateWrapper = viewState.mStateWrapper;
viewState.mStateWrapper = stateWrapper;
ViewManager viewManager = viewState.mViewManager;
ReactViewManagerWrapper viewManager = viewState.mViewManager;
if (viewManager == null) {
throw new IllegalStateException("Unable to find ViewManager for tag: " + reactTag);
@@ -891,7 +907,7 @@ public class SurfaceMountingManager {
}
// For non-root views we notify viewmanager with {@link ViewManager#onDropInstance}
ViewManager viewManager = viewState.mViewManager;
ReactViewManagerWrapper viewManager = viewState.mViewManager;
if (!viewState.mIsRoot && viewManager != null) {
viewManager.onDropViewInstance(viewState.mView);
}
@@ -926,7 +942,7 @@ public class SurfaceMountingManager {
public void preallocateView(
String componentName,
int reactTag,
@Nullable ReadableMap props,
@Nullable Object props,
@Nullable StateWrapper stateWrapper,
@Nullable EventEmitterWrapper eventEmitterWrapper,
boolean isLayoutable) {
@@ -984,7 +1000,7 @@ public class SurfaceMountingManager {
if (viewState.mViewManager == null) {
throw new IllegalStateException("Unable to find ViewManager for view: " + viewState);
}
return (ViewGroupManager<ViewGroup>) viewState.mViewManager;
return (ViewGroupManager<ViewGroup>) viewState.mViewManager.getViewGroupManager();
}
public void printSurfaceState() {
@@ -1014,17 +1030,22 @@ public class SurfaceMountingManager {
@Nullable final View mView;
final int mReactTag;
final boolean mIsRoot;
@Nullable final ViewManager mViewManager;
@Nullable public ReactStylesDiffMap mCurrentProps = null;
@Nullable final ReactViewManagerWrapper mViewManager;
@Nullable public Object mCurrentProps = null;
@Nullable public ReadableMap mCurrentLocalData = null;
@Nullable public StateWrapper mStateWrapper = null;
@Nullable public EventEmitterWrapper mEventEmitter = null;
private ViewState(int reactTag, @Nullable View view, @Nullable ViewManager viewManager) {
private ViewState(
int reactTag, @Nullable View view, @Nullable ReactViewManagerWrapper viewManager) {
this(reactTag, view, viewManager, false);
}
private ViewState(int reactTag, @Nullable View view, ViewManager viewManager, boolean isRoot) {
private ViewState(
int reactTag,
@Nullable View view,
@Nullable ReactViewManagerWrapper viewManager,
boolean isRoot) {
mReactTag = reactTag;
mView = view;
mIsRoot = isRoot;
@@ -16,7 +16,6 @@ import com.facebook.common.logging.FLog;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.MountingManager;
import com.facebook.react.fabric.mounting.SurfaceMountingManager;
@@ -102,10 +101,6 @@ public class IntBufferBatchMountItem implements MountItem {
return obj != null ? (StateWrapper) obj : null;
}
private static ReadableMap castToProps(Object obj) {
return obj != null ? (ReadableMap) obj : null;
}
private static EventEmitterWrapper castToEventEmitter(Object obj) {
return obj != null ? (EventEmitterWrapper) obj : null;
}
@@ -141,7 +136,7 @@ public class IntBufferBatchMountItem implements MountItem {
surfaceMountingManager.createView(
componentName,
mIntBuffer[i++],
castToProps(mObjBuffer[j++]),
mObjBuffer[j++],
castToState(mObjBuffer[j++]),
castToEventEmitter(mObjBuffer[j++]),
mIntBuffer[i++] == 1);
@@ -154,7 +149,7 @@ public class IntBufferBatchMountItem implements MountItem {
} else if (type == INSTRUCTION_REMOVE) {
surfaceMountingManager.removeViewAt(mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]);
} else if (type == INSTRUCTION_UPDATE_PROPS) {
surfaceMountingManager.updateProps(mIntBuffer[i++], castToProps(mObjBuffer[j++]));
surfaceMountingManager.updateProps(mIntBuffer[i++], mObjBuffer[j++]);
} else if (type == INSTRUCTION_UPDATE_STATE) {
surfaceMountingManager.updateState(mIntBuffer[i++], castToState(mObjBuffer[j++]));
} else if (type == INSTRUCTION_UPDATE_LAYOUT) {
@@ -234,10 +229,10 @@ public class IntBufferBatchMountItem implements MountItem {
String.format(
"REMOVE [%d]->[%d] @%d\n", mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]));
} else if (type == INSTRUCTION_UPDATE_PROPS) {
ReadableMap props = castToProps(mObjBuffer[j++]);
Object props = mObjBuffer[j++];
String propsString =
IS_DEVELOPMENT_ENVIRONMENT
? (props != null ? props.toHashMap().toString() : "<null>")
? (props != null ? props.toString() : "<null>")
: "<hidden>";
s.append(String.format("UPDATE PROPS [%d]: %s\n", mIntBuffer[i++], propsString));
} else if (type == INSTRUCTION_UPDATE_STATE) {
@@ -13,7 +13,6 @@ import static com.facebook.react.fabric.FabricUIManager.TAG;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.fabric.events.EventEmitterWrapper;
import com.facebook.react.fabric.mounting.MountingManager;
import com.facebook.react.fabric.mounting.SurfaceMountingManager;
@@ -25,7 +24,7 @@ public class PreAllocateViewMountItem implements MountItem {
@NonNull private final String mComponent;
private final int mSurfaceId;
private final int mReactTag;
private final @Nullable ReadableMap mProps;
private final @Nullable Object mProps;
private final @Nullable StateWrapper mStateWrapper;
private final @Nullable EventEmitterWrapper mEventEmitterWrapper;
private final boolean mIsLayoutable;
@@ -34,7 +33,7 @@ public class PreAllocateViewMountItem implements MountItem {
int surfaceId,
int reactTag,
@NonNull String component,
@Nullable ReadableMap props,
@Nullable Object props,
@NonNull StateWrapper stateWrapper,
@Nullable EventEmitterWrapper eventEmitterWrapper,
boolean isLayoutable) {
@@ -2,16 +2,21 @@ load("//tools/build_defs/oss:rn_defs.bzl", "YOGA_TARGET", "react_native_dep", "r
rn_android_library(
name = "view",
srcs = glob(["*.java"]),
srcs = glob([
"*.java",
"*.kt",
]),
autoglob = False,
is_androidx = True,
labels = ["supermodule:xplat/default/public.react_native.infra"],
language = "KOTLIN",
provided_deps = [
react_native_dep("third-party/android/androidx:core"),
react_native_dep("third-party/android/androidx:fragment"),
react_native_dep("third-party/android/androidx:legacy-support-core-ui"),
react_native_dep("third-party/android/androidx:legacy-support-core-utils"),
],
pure_kotlin = False,
visibility = [
"PUBLIC",
],
@@ -23,6 +28,7 @@ rn_android_library(
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/common/mapbuffer:mapbuffer"),
react_native_target("java/com/facebook/react/config:config"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target("java/com/facebook/react/touch:touch"),
@@ -0,0 +1,474 @@
/*
* 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.views.view
import android.graphics.Color
import android.graphics.Rect
import androidx.core.view.ViewCompat
import com.facebook.react.bridge.DynamicFromObject
import com.facebook.react.bridge.JavaOnlyArray
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.common.mapbuffer.ReadableMapBuffer
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.PointerEvents
object ReactMapBufferPropSetter {
// ViewProps values
private const val VP_ACCESSIBILITY_ACTIONS = 0
private const val VP_ACCESSIBILITY_HINT = 1
private const val VP_ACCESSIBILITY_LABEL = 2
private const val VP_ACCESSIBILITY_LABELLED_BY = 3
private const val VP_ACCESSIBILITY_LIVE_REGION = 4
private const val VP_ACCESSIBILITY_ROLE = 5
private const val VP_ACCESSIBILITY_STATE = 6
private const val VP_ACCESSIBILITY_VALUE = 7
private const val VP_ACCESSIBLE = 8
private const val VP_BACKFACE_VISIBILITY = 9
private const val VP_BG_COLOR = 10
private const val VP_BORDER_COLOR = 11
private const val VP_BORDER_RADII = 12
private const val VP_BORDER_STYLE = 13
private const val VP_COLLAPSABLE = 14
private const val VP_ELEVATION = 15
private const val VP_FOCUSABLE = 16
private const val VP_HAS_TV_FOCUS = 17
private const val VP_HIT_SLOP = 18
private const val VP_IMPORTANT_FOR_ACCESSIBILITY = 19
private const val VP_NATIVE_BACKGROUND = 20
private const val VP_NATIVE_FOREGROUND = 21
private const val VP_NATIVE_ID = 22
private const val VP_OFFSCREEN_ALPHA_COMPOSITING = 23
private const val VP_OPACITY = 24
private const val VP_POINTER_EVENTS = 25
private const val VP_POINTER_ENTER = 26
private const val VP_POINTER_LEAVE = 27
private const val VP_POINTER_MOVE = 28
private const val VP_REMOVE_CLIPPED_SUBVIEW = 29
private const val VP_RENDER_TO_HARDWARE_TEXTURE = 30
private const val VP_SHADOW_COLOR = 31
private const val VP_TEST_ID = 32
private const val VP_TRANSFORM = 33
private const val VP_ZINDEX = 34
// Yoga values
private const val YG_BORDER_WIDTH = 100
private const val YG_OVERFLOW = 101
// AccessibilityAction values
private const val ACCESSIBILITY_ACTION_NAME = 0
private const val ACCESSIBILITY_ACTION_LABEL = 1
// AccessibilityState values
private const val ACCESSIBILITY_STATE_BUSY = 0
private const val ACCESSIBILITY_STATE_DISABLED = 1
private const val ACCESSIBILITY_STATE_EXPANDED = 2
private const val ACCESSIBILITY_STATE_SELECTED = 3
private const val ACCESSIBILITY_STATE_CHECKED = 4
private const val EDGE_TOP = 0
private const val EDGE_LEFT = 1
private const val EDGE_RIGHT = 2
private const val EDGE_BOTTOM = 3
private const val EDGE_START = 4
private const val EDGE_END = 5
private const val EDGE_ALL = 6
private const val CORNER_TOP_LEFT = 0
private const val CORNER_TOP_RIGHT = 1
private const val CORNER_BOTTOM_RIGHT = 2
private const val CORNER_BOTTOM_LEFT = 3
private const val CORNER_TOP_START = 4
private const val CORNER_TOP_END = 5
private const val CORNER_BOTTOM_END = 6
private const val CORNER_BOTTOM_START = 7
private const val CORNER_ALL = 8
private const val NATIVE_DRAWABLE_KIND = 0
private const val NATIVE_DRAWABLE_ATTRIBUTE = 1
private const val NATIVE_DRAWABLE_COLOR = 2
private const val NATIVE_DRAWABLE_BORDERLESS = 3
private const val NATIVE_DRAWABLE_RIPPLE_RADIUS = 4
private const val UNDEF_COLOR = Int.MAX_VALUE
fun setProps(view: ReactViewGroup, viewManager: ReactViewManager, props: ReadableMapBuffer) {
for (entry in props) {
when (entry.key) {
VP_ACCESSIBILITY_ACTIONS -> {
viewManager.accessibilityActions(view, entry.readableMapBuffer)
}
VP_ACCESSIBILITY_HINT -> {
viewManager.setAccessibilityHint(view, entry.string.takeIf { it.isNotEmpty() })
}
VP_ACCESSIBILITY_LABEL -> {
viewManager.setAccessibilityLabel(view, entry.string.takeIf { it.isNotEmpty() })
}
VP_ACCESSIBILITY_LABELLED_BY -> {
viewManager.accessibilityLabelledBy(view, entry.readableMapBuffer)
}
VP_ACCESSIBILITY_LIVE_REGION -> {
view.accessibilityLiveRegion(entry.int)
}
VP_ACCESSIBILITY_ROLE -> {
viewManager.setAccessibilityRole(view, entry.string.takeIf { it.isNotEmpty() })
}
VP_ACCESSIBILITY_STATE -> {
viewManager.accessibilityState(view, entry.readableMapBuffer)
}
VP_ACCESSIBILITY_VALUE -> {
viewManager.accessibilityValue(view, entry.string)
}
VP_ACCESSIBLE -> {
viewManager.setAccessible(view, entry.boolean)
}
VP_BACKFACE_VISIBILITY -> {
viewManager.backfaceVisibility(view, entry.int)
}
VP_BG_COLOR -> {
// TODO: color for some reason can be object in Java but not in C++
viewManager.backgroundColor(view, entry.int)
}
VP_BORDER_COLOR -> {
viewManager.borderColor(view, entry.readableMapBuffer)
}
VP_BORDER_RADII -> {
viewManager.borderRadius(view, entry.readableMapBuffer)
}
VP_BORDER_STYLE -> {
viewManager.borderStyle(view, entry.int)
}
VP_ELEVATION -> {
viewManager.setElevation(view, entry.double.toFloat())
}
VP_FOCUSABLE -> {
viewManager.setFocusable(view, entry.boolean)
}
VP_HAS_TV_FOCUS -> {
viewManager.setTVPreferredFocus(view, entry.boolean)
}
VP_HIT_SLOP -> {
view.hitSlop(entry.readableMapBuffer)
}
VP_IMPORTANT_FOR_ACCESSIBILITY -> {
view.importantForAccessibility(entry.int)
}
VP_NATIVE_BACKGROUND -> {
viewManager.nativeBackground(view, entry.readableMapBuffer)
}
VP_NATIVE_FOREGROUND -> {
viewManager.nativeForeground(view, entry.readableMapBuffer)
}
VP_NATIVE_ID -> {
viewManager.setNativeId(view, entry.string.takeIf { it.isNotEmpty() })
}
VP_OFFSCREEN_ALPHA_COMPOSITING -> {
viewManager.setNeedsOffscreenAlphaCompositing(view, entry.boolean)
}
VP_OPACITY -> {
viewManager.setOpacity(view, entry.double.toFloat())
}
VP_POINTER_EVENTS -> {
view.pointerEvents(entry.int)
}
VP_POINTER_ENTER -> {
viewManager.setPointerEnter(view, entry.boolean)
}
VP_POINTER_LEAVE -> {
viewManager.setPointerLeave(view, entry.boolean)
}
VP_POINTER_MOVE -> {
viewManager.setPointerMove(view, entry.boolean)
}
VP_REMOVE_CLIPPED_SUBVIEW -> {
viewManager.setRemoveClippedSubviews(view, entry.boolean)
}
VP_RENDER_TO_HARDWARE_TEXTURE -> {
viewManager.setRenderToHardwareTexture(view, entry.boolean)
}
VP_SHADOW_COLOR -> {
// TODO: color for some reason can be object in Java but not in C++
viewManager.shadowColor(view, entry.int)
}
VP_TEST_ID -> {
viewManager.setTestId(view, entry.string.takeIf { it.isNotEmpty() })
}
VP_TRANSFORM -> {
viewManager.transform(view, entry.readableMapBuffer)
}
VP_ZINDEX -> {
viewManager.setZIndex(view, entry.int.toFloat())
}
YG_BORDER_WIDTH -> {
viewManager.borderWidth(view, entry.readableMapBuffer)
}
YG_OVERFLOW -> {
viewManager.overflow(view, entry.int)
}
}
}
}
private fun ReactViewManager.accessibilityActions(
view: ReactViewGroup,
mapBuffer: ReadableMapBuffer
) {
val actions = mutableListOf<ReadableMap>()
for (entry in mapBuffer) {
val map = JavaOnlyMap()
val action = entry.readableMapBuffer
if (action != null) {
map.putString("name", action.getString(ACCESSIBILITY_ACTION_NAME))
if (action.hasKey(ACCESSIBILITY_ACTION_LABEL)) {
map.putString("label", action.getString(ACCESSIBILITY_ACTION_LABEL))
}
}
actions.add(map)
}
setAccessibilityActions(view, JavaOnlyArray.from(actions))
}
private fun ReactViewGroup.accessibilityLiveRegion(value: Int) {
val mode =
when (value) {
0 -> ViewCompat.ACCESSIBILITY_LIVE_REGION_NONE
1 -> ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE
2 -> ViewCompat.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
else -> ViewCompat.ACCESSIBILITY_LIVE_REGION_NONE
}
ViewCompat.setAccessibilityLiveRegion(this, mode)
}
private fun ReactViewManager.accessibilityState(view: ReactViewGroup, value: ReadableMapBuffer) {
val accessibilityState = JavaOnlyMap()
accessibilityState.putBoolean("selected", value.getBoolean(ACCESSIBILITY_STATE_SELECTED))
accessibilityState.putBoolean("busy", value.getBoolean(ACCESSIBILITY_STATE_BUSY))
accessibilityState.putBoolean("expanded", value.getBoolean(ACCESSIBILITY_STATE_EXPANDED))
accessibilityState.putBoolean("disabled", value.getBoolean(ACCESSIBILITY_STATE_DISABLED))
when (value.getInt(ACCESSIBILITY_STATE_CHECKED)) {
// Unchecked
0 -> accessibilityState.putBoolean("checked", false)
// Checked
1 -> accessibilityState.putBoolean("checked", true)
// Mixed
2 -> accessibilityState.putString("checked", "mixed")
// 3 -> None
}
setViewState(view, accessibilityState)
}
private fun ReactViewManager.accessibilityValue(view: ReactViewGroup, value: String) {
val map = JavaOnlyMap()
if (value.isNotEmpty()) {
map.putString("text", value)
}
setAccessibilityValue(view, map)
}
private fun ReactViewManager.accessibilityLabelledBy(
view: ReactViewGroup,
value: ReadableMapBuffer
) {
val converted =
if (value.count == 0) {
DynamicFromObject(null)
} else {
val array = JavaOnlyArray()
for (label in value) {
array.pushString(label.string)
}
DynamicFromObject(array)
}
setAccessibilityLabelledBy(view, converted)
}
private fun ReactViewManager.backfaceVisibility(view: ReactViewGroup, value: Int) {
val stringName =
when (value) {
1 -> "visible"
2 -> "hidden"
else -> "auto"
}
setBackfaceVisibility(view, stringName)
}
private fun ReactViewManager.backgroundColor(view: ReactViewGroup, value: Int) {
val color = value.takeIf { it != UNDEF_COLOR } ?: Color.TRANSPARENT
setBackgroundColor(view, color)
}
private fun ReactViewManager.borderColor(view: ReactViewGroup, value: ReadableMapBuffer) {
for (entry in value) {
val index =
when (val key = entry.key) {
EDGE_ALL -> 0
EDGE_LEFT -> 1
EDGE_RIGHT -> 2
EDGE_TOP -> 3
EDGE_BOTTOM -> 4
EDGE_START -> 5
EDGE_END -> 6
else -> throw IllegalArgumentException("Unknown key for border color: $key")
}
val colorValue = entry.int
setBorderColor(view, index, colorValue.takeIf { it != -1 })
}
}
private fun ReactViewManager.borderRadius(view: ReactViewGroup, value: ReadableMapBuffer) {
for (entry in value) {
val index =
when (val key = entry.key) {
CORNER_ALL -> 0
CORNER_TOP_LEFT -> 1
CORNER_TOP_RIGHT -> 2
CORNER_BOTTOM_RIGHT -> 3
CORNER_BOTTOM_LEFT -> 4
CORNER_TOP_START -> 5
CORNER_TOP_END -> 6
CORNER_BOTTOM_START -> 7
CORNER_BOTTOM_END -> 8
else -> throw IllegalArgumentException("Unknown key for border style: $key")
}
val borderRadius = entry.double
if (!borderRadius.isNaN()) {
setBorderRadius(view, index, borderRadius.toFloat())
}
}
}
private fun ReactViewManager.borderStyle(view: ReactViewGroup, value: Int) {
val stringValue =
when (value) {
0 -> "solid"
1 -> "dotted"
2 -> "dashed"
else -> null
}
setBorderStyle(view, stringValue)
}
private fun ReactViewGroup.hitSlop(value: ReadableMapBuffer) {
val rect =
Rect(
PixelUtil.toPixelFromDIP(value.getDouble(EDGE_LEFT)).toInt(),
PixelUtil.toPixelFromDIP(value.getDouble(EDGE_TOP)).toInt(),
PixelUtil.toPixelFromDIP(value.getDouble(EDGE_RIGHT)).toInt(),
PixelUtil.toPixelFromDIP(value.getDouble(EDGE_BOTTOM)).toInt(),
)
hitSlopRect = rect
}
private fun ReactViewGroup.importantForAccessibility(value: Int) {
val mode =
when (value) {
0 -> ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO
1 -> ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES
2 -> ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO
3 -> ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
else -> ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO
}
ViewCompat.setImportantForAccessibility(this, mode)
}
private fun ReactViewGroup.pointerEvents(value: Int) {
val pointerEvents =
when (value) {
0 -> PointerEvents.AUTO
1 -> PointerEvents.NONE
2 -> PointerEvents.BOX_NONE
3 -> PointerEvents.BOX_ONLY
else -> throw IllegalArgumentException("Unknown value for pointer events: $value")
}
setPointerEvents(pointerEvents)
}
private fun ReactViewManager.transform(view: ReactViewGroup, value: ReadableMapBuffer) {
val list = JavaOnlyArray()
for (entry in value) {
list.pushDouble(entry.double)
}
setTransform(view, list)
}
private fun ReactViewManager.borderWidth(view: ReactViewGroup, value: ReadableMapBuffer) {
for (entry in value) {
val index =
when (val key = entry.key) {
EDGE_ALL -> 0
EDGE_LEFT -> 1
EDGE_RIGHT -> 2
EDGE_TOP -> 3
EDGE_BOTTOM -> 4
EDGE_START -> 5
EDGE_END -> 6
else -> throw IllegalArgumentException("Unknown key for border width: $key")
}
val borderWidth = entry.double
if (!borderWidth.isNaN()) {
setBorderWidth(view, index, borderWidth.toFloat())
}
}
}
private fun ReactViewManager.overflow(view: ReactViewGroup, value: Int) {
val stringValue =
when (value) {
0 -> "visible"
1 -> "hidden"
2 -> "scroll"
else -> throw IllegalArgumentException("Unknown overflow value: $value")
}
setOverflow(view, stringValue)
}
private fun ReactViewManager.shadowColor(view: ReactViewGroup, value: Int) {
val color = value.takeIf { it != UNDEF_COLOR } ?: Color.BLACK
setShadowColor(view, color)
}
private fun ReactViewManager.nativeBackground(view: ReactViewGroup, value: ReadableMapBuffer) {
setNativeBackground(view, value.toJsDrawableDescription())
}
private fun ReactViewManager.nativeForeground(view: ReactViewGroup, value: ReadableMapBuffer) {
setNativeForeground(view, value.toJsDrawableDescription())
}
private fun ReadableMapBuffer.toJsDrawableDescription(): ReadableMap? {
if (count == 0) {
return null
}
val kind = getInt(NATIVE_DRAWABLE_KIND)
val result = JavaOnlyMap()
when (kind) {
0 -> {
result.putString("type", "ThemeAttrAndroid")
result.putString("attribute", getString(NATIVE_DRAWABLE_ATTRIBUTE))
}
1 -> {
result.putString("type", "RippleAndroid")
if (hasKey(NATIVE_DRAWABLE_COLOR)) {
result.putInt("color", getInt(NATIVE_DRAWABLE_COLOR))
}
result.putBoolean("borderless", getBoolean(NATIVE_DRAWABLE_BORDERLESS))
if (hasKey(NATIVE_DRAWABLE_RIPPLE_RADIUS)) {
result.putDouble("rippleRadius", getDouble(NATIVE_DRAWABLE_RIPPLE_RADIUS))
}
}
else -> throw IllegalArgumentException("Unknown native drawable: $kind")
}
return result
}
}
@@ -0,0 +1,75 @@
/*
* 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.views.view
import android.view.View
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.common.mapbuffer.ReadableMapBuffer
import com.facebook.react.touch.JSResponderHandler
import com.facebook.react.uimanager.ReactStylesDiffMap
import com.facebook.react.uimanager.StateWrapper
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
object ReactMapBufferViewManager : ReactViewManagerWrapper {
private val viewManager = ReactViewManager()
override fun createView(
reactTag: Int,
reactContext: ThemedReactContext,
props: Any?,
stateWrapper: StateWrapper?,
jsResponderHandler: JSResponderHandler
): View =
viewManager.createView(
reactTag,
reactContext,
props as? ReactStylesDiffMap,
stateWrapper,
jsResponderHandler)
.also { view ->
if (props is ReadableMapBuffer) {
updateProperties(view, props)
}
}
override fun updateProperties(viewToUpdate: View, props: Any?) {
if (props !is ReadableMapBuffer) {
viewManager.updateProperties(viewToUpdate as ReactViewGroup, props as? ReactStylesDiffMap)
} else {
ReactMapBufferPropSetter.setProps(viewToUpdate as ReactViewGroup, viewManager, props)
}
}
override fun receiveCommand(root: View, commandId: String, args: ReadableArray?) {
viewManager.receiveCommand(root as ReactViewGroup, commandId, args)
}
override fun receiveCommand(root: View, commandId: Int, args: ReadableArray?) {
viewManager.receiveCommand(root as ReactViewGroup, commandId, args)
}
override fun setPadding(view: View, left: Int, top: Int, right: Int, bottom: Int) {
viewManager.setPadding(view as ReactViewGroup, left, top, right, bottom)
}
override fun updateState(view: View, props: Any?, stateWrapper: StateWrapper?): Any? = null
override fun updateExtraData(root: View, extraData: Any?) {
viewManager.updateExtraData(root as ReactViewGroup, extraData)
}
override fun onDropViewInstance(view: View) {
viewManager.onDropViewInstance(view as ReactViewGroup)
}
override fun getName(): String = viewManager.name
override val viewGroupManager: ViewGroupManager<*>
get() = viewManager
}
@@ -0,0 +1,90 @@
/*
* 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.views.view
import android.view.View
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.touch.JSResponderHandler
import com.facebook.react.uimanager.ReactStylesDiffMap
import com.facebook.react.uimanager.StateWrapper
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.ViewManager
interface ReactViewManagerWrapper {
fun createView(
reactTag: Int,
reactContext: ThemedReactContext,
props: Any?,
stateWrapper: StateWrapper?,
jsResponderHandler: JSResponderHandler
): View
fun updateProperties(viewToUpdate: View, props: Any?)
fun receiveCommand(root: View, commandId: String, args: ReadableArray?)
fun receiveCommand(root: View, commandId: Int, args: ReadableArray?)
fun setPadding(view: View, left: Int, top: Int, right: Int, bottom: Int)
fun updateState(view: View, props: Any?, stateWrapper: StateWrapper?): Any?
fun updateExtraData(root: View, extraData: Any?)
fun onDropViewInstance(view: View)
fun getName(): String
val viewGroupManager: ViewGroupManager<*>
class DefaultViewManager(private val viewManager: ViewManager<View, *>) :
ReactViewManagerWrapper {
override fun createView(
reactTag: Int,
reactContext: ThemedReactContext,
props: Any?,
stateWrapper: StateWrapper?,
jsResponderHandler: JSResponderHandler
): View =
viewManager.createView(
reactTag, reactContext, props as? ReactStylesDiffMap, stateWrapper, jsResponderHandler)
override fun updateProperties(viewToUpdate: View, props: Any?) {
viewManager.updateProperties(viewToUpdate, props as? ReactStylesDiffMap)
}
override fun receiveCommand(root: View, commandId: String, args: ReadableArray?) {
viewManager.receiveCommand(root, commandId, args)
}
override fun receiveCommand(root: View, commandId: Int, args: ReadableArray?) {
viewManager.receiveCommand(root, commandId, args)
}
override fun setPadding(view: View, left: Int, top: Int, right: Int, bottom: Int) {
viewManager.setPadding(view, left, top, right, bottom)
}
override fun updateState(view: View, props: Any?, stateWrapper: StateWrapper?): Any? =
viewManager.updateState(view, props as? ReactStylesDiffMap, stateWrapper)
override fun updateExtraData(root: View, extraData: Any?) {
viewManager.updateExtraData(root, extraData)
}
override fun onDropViewInstance(view: View) {
viewManager.onDropViewInstance(view)
}
override fun getName(): String = viewManager.name
override val viewGroupManager: ViewGroupManager<*>
get() = viewManager as ViewGroupManager<*>
}
}