From a9a736f2ad624e7a57556bbbae690b1bf014cbc2 Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Tue, 9 Jan 2024 10:11:41 -0800 Subject: [PATCH] Java CxxInspectorPackagerConnection (#42018) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/42018 Creates an JNI wrapper around the C++ version of `InspectorPackagerConnection` (introduced in D52134592), and uses it in React Native Android apps (behind an internal flag that is off by default). In future work, the flag will be turned on by default, then deleted, and eventually the legacy `InspectorPackagerConnection.java` code will be deleted from React Native. Changelog: [Internal] Reviewed By: huntie Differential Revision: D52231237 fbshipit-source-id: 5a0e3bd8b2b711c1c592db15c51df4c3cc89aaad --- .../ReactAndroid/api/ReactAndroid.api | 3 +- .../ReactAndroid/build.gradle.kts | 1 + .../facebook/react/bridge/InspectorFlags.java | 3 + .../CxxInspectorPackagerConnection.java | 142 ++++++++++++++++++ .../react/devsupport/DevServerHelper.java | 12 +- .../react/devsupport/DevSupportSoLoader.java | 22 +++ .../IInspectorPackagerConnection.java | 16 ++ .../InspectorPackagerConnection.java | 2 +- .../ReactAndroid/src/main/jni/CMakeLists.txt | 1 + .../main/jni/react/devsupport/CMakeLists.txt | 19 +++ .../JCxxInspectorPackagerConnection.cpp | 55 +++++++ .../JCxxInspectorPackagerConnection.h | 62 ++++++++ ...nspectorPackagerConnectionDelegateImpl.cpp | 72 +++++++++ ...xInspectorPackagerConnectionDelegateImpl.h | 69 +++++++++ ...xxInspectorPackagerConnectionWebSocket.cpp | 45 ++++++ ...JCxxInspectorPackagerConnectionWebSocket.h | 56 +++++++ ...torPackagerConnectionWebSocketDelegate.cpp | 53 +++++++ ...ectorPackagerConnectionWebSocketDelegate.h | 49 ++++++ .../main/jni/react/devsupport/JOptional.cpp | 29 ++++ .../src/main/jni/react/devsupport/JOptional.h | 27 ++++ .../src/main/jni/react/devsupport/OnLoad.cpp | 20 +++ .../main/jni/react/jni/JInspectorFlags.cpp | 9 ++ .../src/main/jni/react/jni/JInspectorFlags.h | 2 + 23 files changed, 764 insertions(+), 5 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportSoLoader.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/IInspectorPackagerConnection.java create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.cpp create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.h create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.cpp create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.h create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.cpp create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.h create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.cpp create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.h create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.cpp create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.h create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/devsupport/OnLoad.cpp diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index d5b04e79708..440ff3e2b8f 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -733,6 +733,7 @@ public abstract interface class com/facebook/react/bridge/Inspector$RemoteConnec } public class com/facebook/react/bridge/InspectorFlags { + public static fun getEnableCxxInspectorPackagerConnection ()Z public static fun getEnableModernCDPRegistry ()Z } @@ -2197,7 +2198,7 @@ public abstract interface class com/facebook/react/devsupport/HMRClient : com/fa public abstract fun setup (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZ)V } -public class com/facebook/react/devsupport/InspectorPackagerConnection { +public class com/facebook/react/devsupport/InspectorPackagerConnection : com/facebook/react/devsupport/IInspectorPackagerConnection { public fun (Ljava/lang/String;Ljava/lang/String;)V public fun closeQuietly ()V public fun connect ()V diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index 291c0ed7519..19537c13756 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -524,6 +524,7 @@ android { "hermesinstancejni", "uimanagerjni", "jscinstance", + "react_devsupportjni", // prefab targets "reactnativejni", "react_render_debug", diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/InspectorFlags.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/InspectorFlags.java index 2a76037313e..1e7b7bb750c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/InspectorFlags.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/InspectorFlags.java @@ -19,5 +19,8 @@ public class InspectorFlags { @DoNotStrip public static native boolean getEnableModernCDPRegistry(); + @DoNotStrip + public static native boolean getEnableCxxInspectorPackagerConnection(); + private InspectorFlags() {} } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java new file mode 100644 index 00000000000..128fb44551b --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java @@ -0,0 +1,142 @@ +/* + * 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.devsupport; + +import android.os.Handler; +import android.os.Looper; +import androidx.annotation.Nullable; +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import java.io.Closeable; +import java.util.OptionalInt; +import java.util.concurrent.TimeUnit; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; + +/** Java wrapper around a C++ InspectorPackagerConnection. */ +/* package */ class CxxInspectorPackagerConnection implements IInspectorPackagerConnection { + static { + DevSupportSoLoader.staticInit(); + } + + @DoNotStrip private final HybridData mHybridData; + + public CxxInspectorPackagerConnection(String url, String packageName) { + mHybridData = initHybrid(url, packageName, new DelegateImpl()); + } + + private static native HybridData initHybrid( + String url, String packageName, DelegateImpl delegate); + + public native void connect(); + + public native void closeQuietly(); + + public native void sendEventToAllConnections(String event); + + /** Java wrapper around a C++ IWebSocketDelegate, allowing us to call the interface from Java. */ + @DoNotStrip + private static class WebSocketDelegate implements Closeable { + private final HybridData mHybridData; + + public native void didFailWithError(OptionalInt posixCode, String error); + + public native void didReceiveMessage(String message); + + public native void didClose(); + + /** + * Release the C++ part of the hybrid WebSocketDelegate object. This should be called when the + * delegate is not needed anymore ( = the socket will not send more events). + */ + @Override + public void close() { + mHybridData.resetNative(); + } + + @DoNotStrip + private WebSocketDelegate(HybridData hybridData) { + mHybridData = hybridData; + } + } + + /** + * Java counterpart of the C++ IWebSocket interface, allowing us to implement the interface in + * Java. + */ + private interface IWebSocket extends Closeable { + void send(String message); + + /** + * Close the WebSocket connection. NOTE: There is no close() method in the C++ interface. + * Instead, this method is called when the IWebSocket is destroyed on the C++ side. + */ + void close(); + } + + /** Java implementation of the C++ InspectorPackagerConnectionDelegate interface. */ + private static class DelegateImpl { + private final OkHttpClient mHttpClient = + new OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(10, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read + .build(); + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + public IWebSocket connectWebSocket(String url, WebSocketDelegate delegate) { + Request request = new Request.Builder().url(url).build(); + final WebSocket webSocket = + mHttpClient.newWebSocket( + request, + new WebSocketListener() { + @Override + public void onFailure(WebSocket _unused, Throwable t, @Nullable Response response) { + @Nullable String message = t.getMessage(); + delegate.didFailWithError( + OptionalInt.empty(), message != null ? message : ""); + // "No further calls to this listener will be made." -OkHttp docs for + // WebSocketListener.onFailure + delegate.close(); + } + + @Override + public void onMessage(WebSocket _unused, String text) { + delegate.didReceiveMessage(text); + } + + @Override + public void onClosed(WebSocket _unused, int code, String reason) { + delegate.didClose(); + // "No further calls to this listener will be made." -OkHttp docs for + // WebSocketListener.onClosed + delegate.close(); + } + }); + return new IWebSocket() { + @Override + public void send(String message) { + webSocket.send(message); + } + + @Override + public void close() { + webSocket.close(1000, "End of session"); + } + }; + } + + public void scheduleCallback(Runnable runnable, long delayMs) { + mHandler.postDelayed(runnable, delayMs); + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java index 1419c7f1696..31eb28004a0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java @@ -14,6 +14,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.bridge.InspectorFlags; import com.facebook.react.bridge.ReactContext; import com.facebook.react.common.ReactConstants; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; @@ -107,7 +108,7 @@ public class DevServerHelper { private final String mPackageName; private @Nullable JSPackagerClient mPackagerClient; - private @Nullable InspectorPackagerConnection mInspectorPackagerConnection; + private @Nullable IInspectorPackagerConnection mInspectorPackagerConnection; public DevServerHelper( DeveloperSettings developerSettings, @@ -210,8 +211,13 @@ public class DevServerHelper { new AsyncTask() { @Override protected Void doInBackground(Void... params) { - mInspectorPackagerConnection = - new InspectorPackagerConnection(getInspectorDeviceUrl(), mPackageName); + if (InspectorFlags.getEnableCxxInspectorPackagerConnection()) { + mInspectorPackagerConnection = + new CxxInspectorPackagerConnection(getInspectorDeviceUrl(), mPackageName); + } else { + mInspectorPackagerConnection = + new InspectorPackagerConnection(getInspectorDeviceUrl(), mPackageName); + } mInspectorPackagerConnection.connect(); return null; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportSoLoader.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportSoLoader.java new file mode 100644 index 00000000000..4f04fc2f492 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportSoLoader.java @@ -0,0 +1,22 @@ +/* + * 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.devsupport; + +import com.facebook.soloader.SoLoader; + +class DevSupportSoLoader { + private static volatile boolean sDidInit = false; + + public static synchronized void staticInit() { + if (sDidInit) { + return; + } + SoLoader.loadLibrary("react_devsupportjni"); + sDidInit = true; + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/IInspectorPackagerConnection.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/IInspectorPackagerConnection.java new file mode 100644 index 00000000000..59afe06a6f4 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/IInspectorPackagerConnection.java @@ -0,0 +1,16 @@ +/* + * 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.devsupport; + +/* package */ interface IInspectorPackagerConnection { + public void connect(); + + public void closeQuietly(); + + public void sendEventToAllConnections(String event); +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java index 298fc5ce176..84107fb5e4c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/InspectorPackagerConnection.java @@ -27,7 +27,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -public class InspectorPackagerConnection { +public class InspectorPackagerConnection implements IInspectorPackagerConnection { private static final String TAG = "InspectorPackagerConnection"; private final Connection mConnection; diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index feb7a3b33ca..0736ec47faf 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -125,6 +125,7 @@ add_react_android_subdir(src/main/jni/react/runtime/cxxreactpackage) add_react_android_subdir(src/main/jni/react/runtime/jni) add_react_android_subdir(src/main/jni/react/runtime/hermes/jni) add_react_android_subdir(src/main/jni/react/runtime/jsc/jni) +add_react_android_subdir(src/main/jni/react/devsupport) # GTest dependencies add_executable(reactnative_unittest diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt new file mode 100644 index 00000000000..c2c6b29a744 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt @@ -0,0 +1,19 @@ +# 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. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +add_compile_options(-fexceptions -frtti -std=c++20 -Wall -DLOG_TAG=\"ReactNative\") + +file(GLOB react_devsupportjni_SRC CONFIGURE_DEPENDS *.cpp) + +add_library(react_devsupportjni SHARED ${react_devsupportjni_SRC}) + +target_include_directories(react_devsupportjni PUBLIC .) + +target_link_libraries(react_devsupportjni + fbjni + jsinspector) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.cpp new file mode 100644 index 00000000000..ab900a18bd8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.cpp @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#include "JCxxInspectorPackagerConnection.h" + +#include + +using namespace facebook::jni; + +namespace facebook::react::jsinspector_modern { + +JCxxInspectorPackagerConnection::JCxxInspectorPackagerConnection( + const std::string& url, + const std::string& packageName, + alias_ref delegate) + : cxxImpl_(url, packageName, delegate->wrapInUniquePtr()) {} + +local_ref +JCxxInspectorPackagerConnection::initHybrid( + alias_ref, + const std::string& url, + const std::string& packageName, + alias_ref delegate) { + return makeCxxInstance(url, packageName, delegate); +} + +void JCxxInspectorPackagerConnection::connect() { + cxxImpl_.connect(); +} + +void JCxxInspectorPackagerConnection::closeQuietly() { + cxxImpl_.closeQuietly(); +} + +void JCxxInspectorPackagerConnection::sendEventToAllConnections( + const std::string& event) { + cxxImpl_.sendEventToAllConnections(event); +} + +void JCxxInspectorPackagerConnection::registerNatives() { + registerHybrid( + {makeNativeMethod( + "initHybrid", JCxxInspectorPackagerConnection::initHybrid), + makeNativeMethod("connect", JCxxInspectorPackagerConnection::connect), + makeNativeMethod( + "closeQuietly", JCxxInspectorPackagerConnection::closeQuietly), + makeNativeMethod( + "sendEventToAllConnections", + JCxxInspectorPackagerConnection::sendEventToAllConnections)}); +} +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.h b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.h new file mode 100644 index 00000000000..18252ae9d7a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnection.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#pragma once + +#include "JCxxInspectorPackagerConnectionDelegateImpl.h" + +#include +#include + +#include + +namespace facebook::react::jsinspector_modern { + +/** + * A hybrid Java/C++ class that exposes an instance of + * InspectorPackagerConnection to Java. + */ +class JCxxInspectorPackagerConnection + : public jni::HybridClass { + public: + constexpr static auto kJavaDescriptor = + "Lcom/facebook/react/devsupport/CxxInspectorPackagerConnection;"; + + static void registerNatives(); + + // InspectorPackagerConnection's public API + + void connect(); + void closeQuietly(); + void sendEventToAllConnections(const std::string& event); + + private: + friend HybridBase; + + using JDelegateImpl = JCxxInspectorPackagerConnectionDelegateImpl; + + /** + * Private constructor since this class can only be created from Java. + */ + JCxxInspectorPackagerConnection( + const std::string& url, + const std::string& packageName, + jni::alias_ref delegate); + + static jni::local_ref initHybrid( + jni::alias_ref, + const std::string& url, + const std::string& packageName, + jni::alias_ref delegate); + + /** + * The actual C++ implementation wrapped by this hybrid class. + */ + InspectorPackagerConnection cxxImpl_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.cpp new file mode 100644 index 00000000000..d10f8f295e5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.cpp @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#include "JCxxInspectorPackagerConnectionDelegateImpl.h" +#include "JCxxInspectorPackagerConnectionWebSocket.h" +#include "JCxxInspectorPackagerConnectionWebSocketDelegate.h" + +#include + +#include + +using namespace facebook::jni; + +namespace facebook::react::jsinspector_modern { + +std::unique_ptr +JCxxInspectorPackagerConnectionDelegateImpl::connectWebSocket( + const std::string& url, + std::weak_ptr delegate) { + using JWebSocket = JCxxInspectorPackagerConnectionWebSocket; + using JWebSocketDelegate = JCxxInspectorPackagerConnectionWebSocketDelegate; + static auto method = + javaClassStatic() + ->getMethod( + const std::string&, alias_ref)>( + "connectWebSocket"); + auto jWebSocket = method( + self(), url, make_global(JWebSocketDelegate::newObjectCxxArgs(delegate))); + return jWebSocket->wrapInUniquePtr(); +} + +void JCxxInspectorPackagerConnectionDelegateImpl::scheduleCallback( + std::function callback, + std::chrono::milliseconds delayMs) { + static auto method = + javaClassStatic() + ->getMethod, jlong)>( + "scheduleCallback"); + method( + self(), + JNativeRunnable::newObjectCxxArgs(std::move(callback)), + static_cast(delayMs.count())); +} + +std::unique_ptr +JCxxInspectorPackagerConnectionDelegateImpl::wrapInUniquePtr() { + return std::unique_ptr{ + new RefWrapper{self()}}; +} + +JCxxInspectorPackagerConnectionDelegateImpl::RefWrapper::RefWrapper( + jni::alias_ref jDelegate) + : jDelegate_(make_global(jDelegate)) {} + +std::unique_ptr +JCxxInspectorPackagerConnectionDelegateImpl::RefWrapper::connectWebSocket( + const std::string& url, + std::weak_ptr delegate) { + return jDelegate_->connectWebSocket(url, delegate); +} + +void JCxxInspectorPackagerConnectionDelegateImpl::RefWrapper::scheduleCallback( + std::function callback, + std::chrono::milliseconds delayMs) { + return jDelegate_->scheduleCallback(callback, delayMs); +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.h b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.h new file mode 100644 index 00000000000..087c5ecb579 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionDelegateImpl.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace facebook::react::jsinspector_modern { + +/** + * Exposes an instance of the Java class + * CxxInspectorPackagerConnection.DelegateImpl to C++. The public interface + * mirrors InspectorPackagerConnectionDelegate exactly but doesn't inherit from + * it; this is because of fbjni limitations on multiple inheritance. To get a + * usable InspectorPackagerConnectionDelegate instance from this object, call + * wrapInUniquePtr(). + */ +struct JCxxInspectorPackagerConnectionDelegateImpl + : public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/devsupport/CxxInspectorPackagerConnection$DelegateImpl;"; + + std::unique_ptr wrapInUniquePtr(); + + // InspectorPackagerConnectionDelegate methods (mirrored) + + std::unique_ptr connectWebSocket( + const std::string& url, + std::weak_ptr delegate); + + void scheduleCallback( + std::function callback, + std::chrono::milliseconds delayMs); + + private: + class RefWrapper; +}; + +class JCxxInspectorPackagerConnectionDelegateImpl::RefWrapper + : public InspectorPackagerConnectionDelegate { + public: + explicit RefWrapper(jni::alias_ref jDelegate); + + // InspectorPackagerConnectionDelegate methods + + virtual std::unique_ptr connectWebSocket( + const std::string& url, + std::weak_ptr delegate) override; + + virtual void scheduleCallback( + std::function callback, + std::chrono::milliseconds delayMs) override; + + private: + jni::global_ref jDelegate_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.cpp new file mode 100644 index 00000000000..7de47273630 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.cpp @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#include "JCxxInspectorPackagerConnectionWebSocket.h" + +using namespace facebook::jni; +using namespace facebook::react::jsinspector_modern; + +namespace facebook::react::jsinspector_modern { + +void JCxxInspectorPackagerConnectionWebSocket::send(std::string_view message) { + static auto method = + javaClassStatic()->getMethod("send"); + method(self(), std::string(message)); +} + +void JCxxInspectorPackagerConnectionWebSocket::close() { + static auto method = javaClassStatic()->getMethod("close"); + method(self()); +} + +JCxxInspectorPackagerConnectionWebSocket:: + ~JCxxInspectorPackagerConnectionWebSocket() { + close(); +} + +std::unique_ptr +JCxxInspectorPackagerConnectionWebSocket::wrapInUniquePtr() { + return std::make_unique(self()); +} + +JCxxInspectorPackagerConnectionWebSocket::RefWrapper::RefWrapper( + alias_ref jWebSocket) + : jWebSocket_{make_global(jWebSocket)} {} + +void JCxxInspectorPackagerConnectionWebSocket::RefWrapper::send( + std::string_view message) { + jWebSocket_->send(message); +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.h b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.h new file mode 100644 index 00000000000..d73b360fb8a --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocket.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include + +namespace facebook::react::jsinspector_modern { + +/** + * Exposes an implementation of the Java interface + * CxxInspectorPackagerConnection.IWebSocket to C++. The public interface + * mirrors IWebSocket exactly but doesn't inherit from + * it; this is because of fbjni limitations on multiple inheritance. To get a + * usable IWebSocket instance from this object, call + * wrapInUniquePtr(). + */ +class JCxxInspectorPackagerConnectionWebSocket + : public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/devsupport/CxxInspectorPackagerConnection$IWebSocket;"; + + ~JCxxInspectorPackagerConnectionWebSocket(); + + std::unique_ptr wrapInUniquePtr(); + + // IWebSocket methods (mirrored) + void send(std::string_view message); + + private: + class RefWrapper; + + void close(); +}; + +class JCxxInspectorPackagerConnectionWebSocket::RefWrapper : public IWebSocket { + public: + explicit RefWrapper(jni::alias_ref jWebSocket_); + + // IWebSocket methods + + virtual void send(std::string_view message) override; + + private: + jni::global_ref jWebSocket_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.cpp new file mode 100644 index 00000000000..353ea02312d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.cpp @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#include "JCxxInspectorPackagerConnectionWebSocketDelegate.h" + +using namespace facebook::jni; + +namespace facebook::react::jsinspector_modern { + +JCxxInspectorPackagerConnectionWebSocketDelegate:: + JCxxInspectorPackagerConnectionWebSocketDelegate( + std::weak_ptr cxxDelegate) + : cxxDelegate_(cxxDelegate) {} + +void JCxxInspectorPackagerConnectionWebSocketDelegate::didFailWithError( + alias_ref posixCode, + const std::string& error) { + if (auto delegate = cxxDelegate_.lock()) { + delegate->didFailWithError(*posixCode, error); + } +} + +void JCxxInspectorPackagerConnectionWebSocketDelegate::didReceiveMessage( + const std::string& message) { + if (auto delegate = cxxDelegate_.lock()) { + delegate->didReceiveMessage(message); + } +} + +void JCxxInspectorPackagerConnectionWebSocketDelegate::didClose() { + if (auto delegate = cxxDelegate_.lock()) { + delegate->didClose(); + } +} + +void JCxxInspectorPackagerConnectionWebSocketDelegate::registerNatives() { + registerHybrid( + {makeNativeMethod( + "didFailWithError", + JCxxInspectorPackagerConnectionWebSocketDelegate::didFailWithError), + makeNativeMethod( + "didReceiveMessage", + JCxxInspectorPackagerConnectionWebSocketDelegate::didReceiveMessage), + makeNativeMethod( + "didClose", + JCxxInspectorPackagerConnectionWebSocketDelegate::didClose)}); +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.h b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.h new file mode 100644 index 00000000000..d822e51e64f --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JCxxInspectorPackagerConnectionWebSocketDelegate.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include "JOptional.h" + +#include +#include + +#include +#include + +namespace facebook::react::jsinspector_modern { + +/** + * A hybrid Java/C++ class that exposes a C++ implementation of + * IWebSocketDelegate to Java. + */ +class JCxxInspectorPackagerConnectionWebSocketDelegate + : public jni::HybridClass< + JCxxInspectorPackagerConnectionWebSocketDelegate> { + public: + static auto constexpr kJavaDescriptor = + "Lcom/facebook/react/devsupport/CxxInspectorPackagerConnection$WebSocketDelegate;"; + + void didFailWithError( + jni::alias_ref posixCode, + const std::string& error); + + void didReceiveMessage(const std::string& message); + + void didClose(); + + static void registerNatives(); + + JCxxInspectorPackagerConnectionWebSocketDelegate( + std::weak_ptr cxxDelegate); + + private: + friend HybridBase; + std::weak_ptr cxxDelegate_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.cpp new file mode 100644 index 00000000000..17eb2ddac84 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.cpp @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#include "JOptional.h" + +namespace facebook::react { + +int JOptionalInt::getAsInt() const { + static auto method = javaClassStatic()->getMethod("getAsInt"); + return method(self()); +} + +bool JOptionalInt::isPresent() const { + static auto method = javaClassStatic()->getMethod("isPresent"); + return method(self()); +} + +JOptionalInt::operator std::optional() const { + if (!isPresent()) { + return {}; + } + return getAsInt(); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.h b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.h new file mode 100644 index 00000000000..7338df4b138 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/JOptional.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#pragma once + +#include + +#include + +// TODO(moti): Consider moving this into fbjni + +namespace facebook::react { + +class JOptionalInt : public facebook::jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Ljava/util/OptionalInt;"; + + int getAsInt() const; + bool isPresent() const; + operator std::optional() const; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/OnLoad.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/OnLoad.cpp new file mode 100644 index 00000000000..b9f61a958dd --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/OnLoad.cpp @@ -0,0 +1,20 @@ +/* + * 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. + */ + +#include "JCxxInspectorPackagerConnection.h" +#include "JCxxInspectorPackagerConnectionWebSocketDelegate.h" + +#include + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { + return facebook::jni::initialize(vm, [] { + facebook::react::jsinspector_modern::JCxxInspectorPackagerConnection:: + registerNatives(); + facebook::react::jsinspector_modern:: + JCxxInspectorPackagerConnectionWebSocketDelegate::registerNatives(); + }); +} diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.cpp index bbb92abaaf6..607aab0ac23 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.cpp @@ -16,11 +16,20 @@ bool JInspectorFlags::getEnableModernCDPRegistry(jni::alias_ref) { return inspectorFlags.getEnableModernCDPRegistry(); } +bool JInspectorFlags::getEnableCxxInspectorPackagerConnection( + jni::alias_ref) { + auto& inspectorFlags = jsinspector_modern::InspectorFlags::getInstance(); + return inspectorFlags.getEnableCxxInspectorPackagerConnection(); +} + void JInspectorFlags::registerNatives() { javaClassLocal()->registerNatives({ makeNativeMethod( "getEnableModernCDPRegistry", JInspectorFlags::getEnableModernCDPRegistry), + makeNativeMethod( + "getEnableCxxInspectorPackagerConnection", + JInspectorFlags::getEnableCxxInspectorPackagerConnection), }); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.h index 5e2cc2f70b2..7944f836036 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/JInspectorFlags.h @@ -21,6 +21,8 @@ class JInspectorFlags : public jni::JavaClass { static bool getEnableModernCDPRegistry(jni::alias_ref); + static bool getEnableCxxInspectorPackagerConnection(jni::alias_ref); + static void registerNatives(); private: