Kotlinify JavaScriptModuleRegistry (#51164)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51164

As per title.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D74326679

fbshipit-source-id: ecd15fda9ea8686b65171c462e7e82dc9609377b
This commit is contained in:
Fabrizio Cucci
2025-05-07 10:34:14 -07:00
committed by Facebook GitHub Bot
parent 84253c4e20
commit 1908b3dfac
3 changed files with 100 additions and 104 deletions
@@ -921,9 +921,14 @@ public abstract interface class com/facebook/react/bridge/JavaScriptModule {
}
public final class com/facebook/react/bridge/JavaScriptModuleRegistry {
public static final field Companion Lcom/facebook/react/bridge/JavaScriptModuleRegistry$Companion;
public fun <init> ()V
public static fun getJSModuleName (Ljava/lang/Class;)Ljava/lang/String;
public fun getJavaScriptModule (Lcom/facebook/react/bridge/CatalystInstance;Ljava/lang/Class;)Lcom/facebook/react/bridge/JavaScriptModule;
public static final fun getJSModuleName (Ljava/lang/Class;)Ljava/lang/String;
public final fun getJavaScriptModule (Lcom/facebook/react/bridge/CatalystInstance;Ljava/lang/Class;)Lcom/facebook/react/bridge/JavaScriptModule;
}
public final class com/facebook/react/bridge/JavaScriptModuleRegistry$Companion {
public final fun getJSModuleName (Ljava/lang/Class;)Ljava/lang/String;
}
public final class com/facebook/react/bridge/JsonWriterHelper {
@@ -1,102 +0,0 @@
/*
* 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.bridge;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.common.build.ReactBuildConfig;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
/**
* Class responsible for holding all the {@link JavaScriptModule}s. Uses Java proxy objects to
* dispatch method calls on JavaScriptModules to the bridge using the corresponding module and
* method ids so the proper function is executed in JavaScript.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
public final class JavaScriptModuleRegistry {
private final HashMap<Class<? extends JavaScriptModule>, JavaScriptModule> mModuleInstances;
public JavaScriptModuleRegistry() {
mModuleInstances = new HashMap<>();
}
public synchronized <T extends JavaScriptModule> T getJavaScriptModule(
CatalystInstance instance, Class<T> moduleInterface) {
JavaScriptModule module = mModuleInstances.get(moduleInterface);
if (module != null) {
return (T) module;
}
JavaScriptModule interfaceProxy =
(JavaScriptModule)
Proxy.newProxyInstance(
moduleInterface.getClassLoader(),
new Class[] {moduleInterface},
new JavaScriptModuleInvocationHandler(instance, moduleInterface));
mModuleInstances.put(moduleInterface, interfaceProxy);
return (T) interfaceProxy;
}
private static class JavaScriptModuleInvocationHandler implements InvocationHandler {
private final CatalystInstance mCatalystInstance;
private final Class<? extends JavaScriptModule> mModuleInterface;
private @Nullable String mName;
public JavaScriptModuleInvocationHandler(
CatalystInstance catalystInstance, Class<? extends JavaScriptModule> moduleInterface) {
mCatalystInstance = catalystInstance;
mModuleInterface = moduleInterface;
if (ReactBuildConfig.DEBUG) {
Set<String> methodNames = new HashSet<>();
for (Method method : mModuleInterface.getDeclaredMethods()) {
if (!methodNames.add(method.getName())) {
throw new AssertionError(
"Method overloading is unsupported: "
+ mModuleInterface.getName()
+ "#"
+ method.getName());
}
}
}
}
private String getJSModuleName() {
if (mName == null) {
// Getting the class name every call is expensive, so cache it
mName = JavaScriptModuleRegistry.getJSModuleName(mModuleInterface);
}
return mName;
}
@Override
public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
throws Throwable {
NativeArray jsArgs = args != null ? Arguments.fromJavaArgs(args) : new WritableNativeArray();
mCatalystInstance.callFunction(getJSModuleName(), method.getName(), jsArgs);
return null;
}
}
public static String getJSModuleName(Class<? extends JavaScriptModule> jsModuleInterface) {
// With proguard obfuscation turned on, proguard apparently (poorly) emulates inner
// classes or something because Class#getSimpleName() no longer strips the outer
// class name. We manually strip it here if necessary.
String name = jsModuleInterface.getSimpleName();
int dollarSignIndex = name.lastIndexOf('$');
if (dollarSignIndex != -1) {
name = name.substring(dollarSignIndex + 1);
}
return name;
}
}
@@ -0,0 +1,93 @@
/*
* 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.bridge
import com.facebook.react.common.build.ReactBuildConfig
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
/**
* Class responsible for holding all the [JavaScriptModule]s. Uses Java proxy objects to dispatch
* method calls on JavaScriptModules to the bridge using the corresponding module and method ids so
* the proper function is executed in JavaScript.
*/
public class JavaScriptModuleRegistry {
private val moduleInstances: MutableMap<Class<out JavaScriptModule>, JavaScriptModule> = HashMap()
@Synchronized
public fun <T : JavaScriptModule> getJavaScriptModule(
@Suppress("DEPRECATION") instance: CatalystInstance,
moduleInterface: Class<T>
): T {
val module = moduleInstances[moduleInterface]
if (module != null) {
@Suppress("UNCHECKED_CAST")
return module as T
}
val proxy =
Proxy.newProxyInstance(
moduleInterface.classLoader,
arrayOf<Class<*>>(moduleInterface),
JavaScriptModuleInvocationHandler(instance, moduleInterface)) as JavaScriptModule
moduleInstances[moduleInterface] = proxy
@Suppress("UNCHECKED_CAST")
return proxy as T
}
private class JavaScriptModuleInvocationHandler(
@Suppress("DEPRECATION") private val catalystInstance: CatalystInstance,
private val moduleInterface: Class<out JavaScriptModule>
) : InvocationHandler {
private var name: String? = null
init {
if (ReactBuildConfig.DEBUG) {
val methodNames = mutableSetOf<String>()
for (method in moduleInterface.declaredMethods) {
if (!methodNames.add(method.name)) {
throw AssertionError(
"Method overloading is unsupported: ${moduleInterface.name}#${method.name}")
}
}
}
}
private fun getJSModuleName(): String {
// Getting the class name every call is expensive, so cache it
return name ?: getJSModuleName(moduleInterface).also { name = it }
}
public override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
val jsArgs = if (args != null) Arguments.fromJavaArgs(args) else WritableNativeArray()
catalystInstance.callFunction(getJSModuleName(), method.name, jsArgs)
return null
}
}
public companion object {
/**
* With proguard obfuscation turned on, proguard apparently (poorly) emulates inner classes or
* something because Class#getSimpleName() no longer strips the outer class name. We manually
* strip it here if necessary.
*/
@JvmStatic
public fun getJSModuleName(jsModuleInterface: Class<out JavaScriptModule>): String {
var name = jsModuleInterface.simpleName
val dollarSignIndex = name.lastIndexOf('$')
if (dollarSignIndex != -1) {
name = name.substring(dollarSignIndex + 1)
}
return name
}
}
}