diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/BUCK b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/BUCK new file mode 100644 index 000000000..ac6ce3d53 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/BUCK @@ -0,0 +1,24 @@ +load("@xplat//ReactNative:DEFS.bzl", "react_native_dep", "react_native_target", "rn_android_library", "IS_OSS_BUILD") + +rn_android_library( + name = "jsc", + srcs = glob(["**/*.java"]), + exported_deps = [ + react_native_dep("java/com/facebook/jni:jni"), + react_native_dep("java/com/facebook/proguard/annotations:annotations"), + ], + provided_deps = [ + react_native_dep("third-party/android/support/v4:lib-support-v4"), + ], + required_for_source_only_abi = True, + visibility = [ + "PUBLIC", + ], + deps = [ + react_native_dep("libraries/soloader/java/com/facebook/soloader:soloader"), + react_native_dep("third-party/java/infer-annotations:infer-annotations"), + react_native_target("java/com/facebook/react/bridge:bridge"), + react_native_target("java/com/facebook/react/fabric:fabric"), + react_native_target("java/com/facebook/react/fabric/jsc/jni:jni"), + ], +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/FabricJSCBinding.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/FabricJSCBinding.java new file mode 100644 index 000000000..ab7352f9e --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/FabricJSCBinding.java @@ -0,0 +1,35 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.fabric.jsc; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.JavaScriptContextHolder; +import com.facebook.react.fabric.FabricBinding; +import com.facebook.react.fabric.FabricUIManager; +import com.facebook.soloader.SoLoader; + +@DoNotStrip +public class FabricJSCBinding implements FabricBinding { + + static { + SoLoader.loadLibrary("fabricjscjni"); + } + + // used from native + @SuppressWarnings("unused") + private final HybridData mHybridData; + + private static native HybridData initHybrid(); + + private native void installFabric(long jsContextNativePointer, Object fabricModule); + + public FabricJSCBinding() { + mHybridData = initHybrid(); + } + + @Override + public void installFabric(JavaScriptContextHolder jsContext, FabricUIManager fabricModule) { + installFabric(jsContext.get(), fabricModule); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/BUCK b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/BUCK new file mode 100644 index 000000000..cbf088f2d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/BUCK @@ -0,0 +1,20 @@ +load("@xplat//ReactNative:DEFS.bzl", "react_native_target", "react_native_xplat_target", "rn_xplat_cxx_library", "FBJNI_TARGET", "ANDROID") + +rn_xplat_cxx_library( + name = "jni", + srcs = glob(["*.cpp"]), + headers = glob(["*.h"]), + compiler_flags = [ + "-Wall", + "-fexceptions", + "-std=gnu++1y", + ], + platforms = ANDROID, + soname = "libfabricjscjni.$(ext)", + visibility = ["PUBLIC"], + deps = [ + FBJNI_TARGET, + react_native_xplat_target("jschelpers:jschelpers"), + react_native_target("jni/react/jni:jni"), + ], +) diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/FabricJSCBinding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/FabricJSCBinding.cpp new file mode 100644 index 000000000..044143210 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/FabricJSCBinding.cpp @@ -0,0 +1,296 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "FabricJSCBinding.h" +#include +#include +#include +#include + +using namespace facebook::jni; + +namespace facebook { +namespace react { + +namespace { + +bool useCustomJSC = false; + +struct JList : public JavaClass { + static constexpr auto kJavaDescriptor = "Ljava/util/List;"; +}; + +struct JShadowNode : public JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/facebook/react/uimanager/ReactShadowNode;"; +}; + +typedef struct FabricJSCUIManager { + FabricJSCUIManager(alias_ref module, JSClassRef classRef, bool customJSC) + : wrapperObjectClassRef(classRef) + , useCustomJSC(customJSC) { + fabricUiManager = make_global(module); + } + global_ref fabricUiManager; + JSClassRef wrapperObjectClassRef; + bool useCustomJSC; +} FabricJSCUIManager; + +jobject makePlainGlobalRef(jobject object) { + // When storing the global reference we need it to be a plain + // pointer. That's why we use plain jni instead of fbjni here. + return Environment::current()->NewGlobalRef(object); +} + +local_ref JSValueToJString(JSContextRef ctx, JSValueRef value) { + JSStringRef strRef = JSC_JSValueToStringCopy(ctx, value, NULL); + const size_t size = JSStringGetMaximumUTF8CStringSize(strRef); + char buffer[size]; + JSStringGetUTF8CString(strRef, buffer, size); + JSC_JSStringRelease(ctx, strRef); + return make_jstring(buffer); +} + +local_ref JSValueToJShadowNode(JSContextRef ctx, JSValueRef value) { + JSObjectRef obj = JSC_JSValueToObject(ctx, value, NULL); + auto node = static_cast(JSC_JSObjectGetPrivate(useCustomJSC, obj)); + return make_local(node); +} + +local_ref JSValueToJList(JSContextRef ctx, JSValueRef value) { + JSObjectRef obj = JSC_JSValueToObject(ctx, value, NULL); + auto node = static_cast(JSC_JSObjectGetPrivate(useCustomJSC, obj)); + return make_local(node); +} + +local_ref JSValueToReadableMapViaJSON(JSContextRef ctx, JSValueRef value) { + JSStringRef jsonRef = JSC_JSValueCreateJSONString(ctx, value, 0, NULL); + size_t size = JSC_JSStringGetLength(ctx, jsonRef); + const JSChar* utf16 = JSC_JSStringGetCharactersPtr(ctx, jsonRef); + std::string json = unicode::utf16toUTF8(utf16, size); + JSC_JSStringRelease(ctx, jsonRef); + folly::dynamic dynamicValue = folly::parseJson(json); + return ReadableNativeMap::newObjectCxxArgs(std::move(dynamicValue)); +} + +JSValueRef createNode(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function); + alias_ref manager = managerWrapper->fabricUiManager; + JSClassRef classRef = managerWrapper->wrapperObjectClassRef; + + static auto createNode = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod(jint, jstring, jint, ReadableNativeMap::javaobject)>("createNode"); + + int reactTag = (int)JSC_JSValueToNumber(ctx, arguments[0], NULL); + auto viewName = JSValueToJString(ctx, arguments[1]); + int rootTag = (int)JSC_JSValueToNumber(ctx, arguments[2], NULL); + auto props = JSC_JSValueIsNull(ctx, arguments[3]) ? local_ref(nullptr) : + JSValueToReadableMapViaJSON(ctx, arguments[3]);; + + // TODO: Retain object in arguments[4] using a weak ref. + + auto node = createNode(manager, reactTag, viewName.get(), rootTag, props.get()); + + return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(node.get())); +} + +JSValueRef cloneNode(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function); + alias_ref manager = managerWrapper->fabricUiManager; + JSClassRef classRef = managerWrapper->wrapperObjectClassRef; + + static auto cloneNode = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod(JShadowNode::javaobject)>("cloneNode"); + + auto previousNode = JSValueToJShadowNode(ctx, arguments[0]); + auto newNode = cloneNode(manager, previousNode.get()); + + return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(newNode.get())); +} + +JSValueRef cloneNodeWithNewChildren(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function); + alias_ref manager = managerWrapper->fabricUiManager; + JSClassRef classRef = managerWrapper->wrapperObjectClassRef; + + static auto cloneNodeWithNewChildren = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod(JShadowNode::javaobject)>("cloneNodeWithNewChildren"); + + auto previousNode = JSValueToJShadowNode(ctx, arguments[0]); + auto newNode = cloneNodeWithNewChildren(manager, previousNode.get()); + + return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(newNode.get())); +} + +JSValueRef cloneNodeWithNewProps(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function); + alias_ref manager = managerWrapper->fabricUiManager; + JSClassRef classRef = managerWrapper->wrapperObjectClassRef; + + static auto cloneNodeWithNewProps = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod(JShadowNode::javaobject, ReadableNativeMap::javaobject)>("cloneNodeWithNewProps"); + + auto previousNode = JSValueToJShadowNode(ctx, arguments[0]); + auto props = JSValueToReadableMapViaJSON(ctx, arguments[1]); + auto newNode = cloneNodeWithNewProps(manager, previousNode.get(), props.get()); + + return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(newNode.get())); +} + +JSValueRef cloneNodeWithNewChildrenAndProps(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function); + alias_ref manager = managerWrapper->fabricUiManager; + JSClassRef classRef = managerWrapper->wrapperObjectClassRef; + + static auto cloneNodeWithNewChildrenAndProps = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod(JShadowNode::javaobject, ReadableNativeMap::javaobject)>("cloneNodeWithNewChildrenAndProps"); + + auto previousNode = JSValueToJShadowNode(ctx, arguments[0]); + auto props = JSValueToReadableMapViaJSON(ctx, arguments[1]); + auto newNode = cloneNodeWithNewChildrenAndProps(manager, previousNode.get(), props.get()); + + return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(newNode.get())); +} + +JSValueRef appendChild(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function); + alias_ref manager = managerWrapper->fabricUiManager; + + static auto appendChild = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod("appendChild"); + + auto parentNode = JSValueToJShadowNode(ctx, arguments[0]); + auto childNode = JSValueToJShadowNode(ctx, arguments[1]); + + appendChild(manager, parentNode.get(), childNode.get()); + + return JSC_JSValueMakeUndefined(ctx); +} + +JSValueRef createChildSet(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function); + alias_ref manager = managerWrapper->fabricUiManager; + JSClassRef classRef = managerWrapper->wrapperObjectClassRef; + + static auto createChildSet = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod(jint)>("createChildSet"); + + int rootTag = (int)JSC_JSValueToNumber(ctx, arguments[0], NULL); + auto childSet = createChildSet(manager, rootTag); + + return JSC_JSObjectMake(ctx, classRef, makePlainGlobalRef(childSet.get())); +} + +JSValueRef appendChildToSet(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function); + alias_ref manager = managerWrapper->fabricUiManager; + + static auto appendChildToSet = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod("appendChildToSet"); + + auto childSet = JSValueToJList(ctx, arguments[0]); + auto childNode = JSValueToJShadowNode(ctx, arguments[1]); + + appendChildToSet(manager, childSet.get(), childNode.get()); + + return JSC_JSValueMakeUndefined(ctx); +} + +JSValueRef completeRoot(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception) { + FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, function); + alias_ref manager = managerWrapper->fabricUiManager; + + static auto completeRoot = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod("completeRoot"); + + int rootTag = (int)JSC_JSValueToNumber(ctx, arguments[0], NULL); + auto childSet = JSValueToJList(ctx, arguments[1]); + + completeRoot(manager, rootTag, childSet.get()); + + return JSC_JSValueMakeUndefined(ctx); +} + +void finalizeJNIObject(JSObjectRef object) { + // Release whatever global ref object we're storing here. + jobject globalRef = (jobject)JSC_JSObjectGetPrivate(useCustomJSC, object); + Environment::current()->DeleteGlobalRef(globalRef); +} + +void finalizeWrapper(JSObjectRef object) { + FabricJSCUIManager *managerWrapper = (FabricJSCUIManager *)JSC_JSObjectGetPrivate(useCustomJSC, object); + delete managerWrapper; +} + +void addFabricMethod( + JSContextRef context, + jni::alias_ref fabricModule, + JSClassRef nodeClassRef, + JSObjectRef module, + const char *name, + JSObjectCallAsFunctionCallback callback +) { + JSClassDefinition definition = kJSClassDefinitionEmpty; + definition.callAsFunction = callback; + definition.finalize = finalizeWrapper; + JSClassRef classRef = JSC_JSClassCreate(useCustomJSC, &definition); + FabricJSCUIManager *managerWrapper = new FabricJSCUIManager(fabricModule, nodeClassRef, useCustomJSC); + JSObjectRef functionRef = JSC_JSObjectMake(context, classRef, managerWrapper); + JSC_JSClassRelease(useCustomJSC, classRef); + + JSStringRef nameStr = JSC_JSStringCreateWithUTF8CString(context, name); + JSC_JSObjectSetProperty(context, module, nameStr, functionRef, kJSPropertyAttributeNone, NULL); + JSC_JSStringRelease(context, nameStr); +} + +} + +jni::local_ref FabricJSCBinding::initHybrid( + jni::alias_ref) { + return makeCxxInstance(); +} + +void FabricJSCBinding::installFabric(jlong jsContextNativePointer, + jni::alias_ref fabricModule) { + JSContextRef context = (JSContextRef)jsContextNativePointer; + useCustomJSC = facebook::react::isCustomJSCPtr(context); + + JSObjectRef module = JSC_JSObjectMake(context, NULL, NULL); + + // Class definition for wrapper objects around nodes and sets + JSClassDefinition definition = kJSClassDefinitionEmpty; + definition.finalize = finalizeJNIObject; + JSClassRef classRef = JSC_JSClassCreate(useCustomJSC, &definition); + + addFabricMethod(context, fabricModule, classRef, module, "createNode", createNode); + addFabricMethod(context, fabricModule, classRef, module, "cloneNode", cloneNode); + addFabricMethod(context, fabricModule, classRef, module, "cloneNodeWithNewChildren", cloneNodeWithNewChildren); + addFabricMethod(context, fabricModule, classRef, module, "cloneNodeWithNewProps", cloneNodeWithNewProps); + addFabricMethod(context, fabricModule, classRef, module, "cloneNodeWithNewChildrenAndProps", cloneNodeWithNewChildrenAndProps); + addFabricMethod(context, fabricModule, classRef, module, "appendChild", appendChild); + addFabricMethod(context, fabricModule, classRef, module, "createChildSet", createChildSet); + addFabricMethod(context, fabricModule, classRef, module, "appendChildToSet", appendChildToSet); + addFabricMethod(context, fabricModule, classRef, module, "completeRoot", completeRoot); + + JSObjectRef globalObject = JSC_JSContextGetGlobalObject(context); + JSStringRef globalName = JSC_JSStringCreateWithUTF8CString(context, "nativeFabricUIManager"); + JSC_JSObjectSetProperty(context, globalObject, globalName, module, kJSPropertyAttributeNone, NULL); + JSC_JSStringRelease(context, globalName); +} + +void FabricJSCBinding::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", FabricJSCBinding::initHybrid), + makeNativeMethod("installFabric", FabricJSCBinding::installFabric), + }); +} + +} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/FabricJSCBinding.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/FabricJSCBinding.h new file mode 100644 index 000000000..e23c9524e --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/FabricJSCBinding.h @@ -0,0 +1,29 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#pragma once + +#include +#include + +namespace facebook { +namespace react { + +class Instance; + +class FabricJSCBinding : public jni::HybridClass { +public: + constexpr static const char *const kJavaDescriptor = + "Lcom/facebook/react/fabric/jsc/FabricJSCBinding;"; + + static void registerNatives(); + +private: + + static jni::local_ref initHybrid(jni::alias_ref); + + void installFabric(jlong jsContextNativePointer, jni::alias_ref fabricModule); + +}; + +} +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/OnLoad.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/OnLoad.cpp new file mode 100644 index 000000000..1ca90c4e9 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jsc/jni/OnLoad.cpp @@ -0,0 +1,12 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include +#include + +#include "FabricJSCBinding.h" + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { + return facebook::xplat::initialize(vm, [] { + facebook::react::FabricJSCBinding::registerNatives(); + }); +}