diff --git a/React/Executors/RCTJSCExecutor.h b/React/Executors/RCTJSCExecutor.h index b34218f2b..f8beaa673 100644 --- a/React/Executors/RCTJSCExecutor.h +++ b/React/Executors/RCTJSCExecutor.h @@ -16,6 +16,15 @@ */ RCT_EXTERN NSString *const RCTJSCThreadName; +/** + * This notification fires on the JS thread immediately after a `JSContext` + * is fully initialized, but before the JS bundle has been loaded. The object + * of this notification is the `JSContext`. Native modules should listen for + * notification only if they need to install custom functionality into the + * context. Note that this notification won't fire when debugging in Chrome. + */ +RCT_EXTERN NSString *const RCTJavaScriptContextCreatedNotification; + /** * Uses a JavaScriptCore context as the execution engine. */ diff --git a/React/Executors/RCTJSCExecutor.m b/React/Executors/RCTJSCExecutor.m index db08f806e..91c915a90 100644 --- a/React/Executors/RCTJSCExecutor.m +++ b/React/Executors/RCTJSCExecutor.m @@ -28,6 +28,8 @@ NSString *const RCTJSCThreadName = @"com.facebook.React.JavaScript"; +NSString *const RCTJavaScriptContextCreatedNotification = @"RCTJavaScriptContextCreatedNotification"; + static NSString *const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled"; @interface RCTJavaScriptContext : NSObject @@ -332,7 +334,16 @@ static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context) }]; [self executeBlockOnJavaScriptQueue:^{ - RCTInstallJSCProfiler(_bridge, self.context.ctx); + RCTJSCExecutor *strongSelf = weakSelf; + if (!strongSelf.valid) { + return; + } + + JSContext *context = strongSelf.context.context; + RCTInstallJSCProfiler(_bridge, context.JSGlobalContextRef); + + [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification + object:context]; }]; for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java index 1a84c05fa..6d05c003a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java @@ -332,6 +332,11 @@ public abstract class BaseJavaModule implements NativeModule { return false; } + @Override + public void onReactBridgeInitialized(ReactBridge bridge) { + // do nothing + } + @Override public void onCatalystInstanceDestroy() { // do nothing diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index d3bcc1abd..cc34bf6ff 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -127,6 +127,7 @@ public class CatalystInstanceImpl implements CatalystInstance { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } + mJavaRegistry.notifyReactBridgeInitialized(bridge); return bridge; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java index 905b5be93..5dae9ac3e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java @@ -59,6 +59,15 @@ public interface NativeModule { */ boolean canOverrideExistingModule(); + /** + * Called on the JS thread after a ReactBridge has been created. This is useful for native modules + * that need to do any setup before the JS bundle has been loaded. An example of this would be + * installing custom functionality into the JavaScriptCore context. + * + * @param bridge the ReactBridge instance that has just been created + */ + void onReactBridgeInitialized(ReactBridge bridge); + /** * Called before {CatalystInstance#onHostDestroy} */ diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java index 389fd66b4..131def5fb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java @@ -114,6 +114,19 @@ public class NativeModuleRegistry { } } + /* package */ void notifyReactBridgeInitialized(ReactBridge bridge) { + Systrace.beginSection( + Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, + "NativeModuleRegistry_notifyReactBridgeInitialized"); + try { + for (NativeModule nativeModule : mModuleInstances.values()) { + nativeModule.onReactBridgeInitialized(bridge); + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); + } + } + public void onBatchComplete() { for (int i = 0; i < mBatchCompleteListenerModules.size(); i++) { mBatchCompleteListenerModules.get(i).onBatchComplete(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java index b809e680b..c088de3a6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java @@ -87,4 +87,13 @@ public class ReactBridge extends Countable { public native void stopProfiler(String title, String filename); private native void handleMemoryPressureModerate(); private native void handleMemoryPressureCritical(); + + /** + * This method will return a long representing the underlying JSGlobalContextRef pointer or + * 0 (representing NULL) when in Chrome debug mode, and is only useful if passed back through + * the JNI to native code that will use it with the JavaScriptCore C API. + * **WARNING:** This method is *experimental* and should only be used when no other option is + * available. It will likely change in a future release! + */ + public native long getJavaScriptContextNativePtrExperimental(); } diff --git a/ReactAndroid/src/main/jni/react/Bridge.cpp b/ReactAndroid/src/main/jni/react/Bridge.cpp index a8ee4c2f5..fe835fa51 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.cpp +++ b/ReactAndroid/src/main/jni/react/Bridge.cpp @@ -57,6 +57,10 @@ void Bridge::setGlobalVariable(const std::string& propName, const std::string& j m_mainExecutor->setGlobalVariable(propName, jsonValue); } +void* Bridge::getJavaScriptContext() { + return m_mainExecutor->getJavaScriptContext(); +} + bool Bridge::supportsProfiling() { return m_mainExecutor->supportsProfiling(); } diff --git a/ReactAndroid/src/main/jni/react/Bridge.h b/ReactAndroid/src/main/jni/react/Bridge.h index d9c108a30..1833ccba4 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.h +++ b/ReactAndroid/src/main/jni/react/Bridge.h @@ -54,6 +54,7 @@ public: const std::string& startupCode, const std::string& sourceURL); void setGlobalVariable(const std::string& propName, const std::string& jsonValue); + void* getJavaScriptContext(); bool supportsProfiling(); void startProfiler(const std::string& title); void stopProfiler(const std::string& title, const std::string& filename); diff --git a/ReactAndroid/src/main/jni/react/Executor.h b/ReactAndroid/src/main/jni/react/Executor.h index 8da103630..036b6414e 100644 --- a/ReactAndroid/src/main/jni/react/Executor.h +++ b/ReactAndroid/src/main/jni/react/Executor.h @@ -60,6 +60,9 @@ public: virtual void setGlobalVariable( const std::string& propName, const std::string& jsonValue) = 0; + virtual void* getJavaScriptContext() { + return nullptr; + }; virtual bool supportsProfiling() { return false; }; diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index 2398988ba..ceb2564d5 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -198,6 +198,10 @@ void JSCExecutor::setGlobalVariable(const std::string& propName, const std::stri JSObjectSetProperty(m_context, globalObject, jsPropertyName, valueToInject, 0, NULL); } +void* JSCExecutor::getJavaScriptContext() { + return m_context; +} + bool JSCExecutor::supportsProfiling() { #ifdef WITH_FBSYSTRACE return true; diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.h b/ReactAndroid/src/main/jni/react/JSCExecutor.h index 41558bd4a..dbba889ab 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.h +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.h @@ -48,6 +48,7 @@ public: virtual void setGlobalVariable( const std::string& propName, const std::string& jsonValue) override; + virtual void* getJavaScriptContext() override; virtual bool supportsProfiling() override; virtual void startProfiler(const std::string &titleString) override; virtual void stopProfiler(const std::string &titleString, const std::string &filename) override; diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 6fe35d73c..1e4c37950 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -760,6 +760,11 @@ static void setGlobalVariable(JNIEnv* env, jobject obj, jstring propName, jstrin bridge->setGlobalVariable(fromJString(env, propName), fromJString(env, jsonValue)); } +static jlong getJavaScriptContext(JNIEnv *env, jobject obj) { + auto bridge = extractRefPtr(env, obj); + return (uintptr_t) bridge->getJavaScriptContext(); +} + static jboolean supportsProfiling(JNIEnv* env, jobject obj) { auto bridge = extractRefPtr(env, obj); return bridge->supportsProfiling() ? JNI_TRUE : JNI_FALSE; @@ -944,7 +949,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { makeNativeMethod("stopProfiler", bridge::stopProfiler), makeNativeMethod("handleMemoryPressureModerate", bridge::handleMemoryPressureModerate), makeNativeMethod("handleMemoryPressureCritical", bridge::handleMemoryPressureCritical), - + makeNativeMethod("getJavaScriptContextNativePtrExperimental", bridge::getJavaScriptContext), }); jclass nativeRunnableClass = env->FindClass("com/facebook/react/bridge/queue/NativeRunnableDeprecated");