|
| 1 | +// Copyright 2004-present Facebook. All Rights Reserved. |
| 2 | + |
| 3 | +#include "CatalystInstanceImpl.h" |
| 4 | + |
| 5 | +#include <mutex> |
| 6 | +#include <condition_variable> |
| 7 | + |
| 8 | +#include <folly/dynamic.h> |
| 9 | +#include <folly/Memory.h> |
| 10 | + |
| 11 | +#include <fb/log.h> |
| 12 | + |
| 13 | +#include <jni/Countable.h> |
| 14 | +#include <jni/LocalReference.h> |
| 15 | + |
| 16 | +#include <react/jni/NativeArray.h> |
| 17 | + |
| 18 | +#include <cxxreact/Instance.h> |
| 19 | +#include <cxxreact/MethodCall.h> |
| 20 | +#include <cxxreact/ModuleRegistry.h> |
| 21 | + |
| 22 | +#include "JSLoader.h" |
| 23 | +#include "JavaScriptExecutorHolder.h" |
| 24 | +#include "JniJSModulesUnbundle.h" |
| 25 | +#include "ModuleRegistryHolder.h" |
| 26 | +#include "JNativeRunnable.h" |
| 27 | + |
| 28 | +using namespace facebook::jni; |
| 29 | + |
| 30 | +namespace facebook { |
| 31 | +namespace react { |
| 32 | + |
| 33 | +namespace { |
| 34 | + |
| 35 | + |
| 36 | +class Exception : public jni::JavaClass<Exception> { |
| 37 | + public: |
| 38 | + static auto constexpr kJavaDescriptor = "Ljava/lang/Exception;"; |
| 39 | +}; |
| 40 | + |
| 41 | +class JInstanceCallback : public InstanceCallback { |
| 42 | + public: |
| 43 | + explicit JInstanceCallback(alias_ref<ReactCallback::javaobject> jobj) |
| 44 | + : jobj_(make_global(jobj)) {} |
| 45 | + |
| 46 | + void onBatchComplete() override { |
| 47 | + static auto method = |
| 48 | + ReactCallback::javaClassStatic()->getMethod<void()>("onBatchComplete"); |
| 49 | + method(jobj_); |
| 50 | + } |
| 51 | + |
| 52 | + void incrementPendingJSCalls() override { |
| 53 | + static auto method = |
| 54 | + ReactCallback::javaClassStatic()->getMethod<void()>("incrementPendingJSCalls"); |
| 55 | + method(jobj_); |
| 56 | + } |
| 57 | + |
| 58 | + void decrementPendingJSCalls() override { |
| 59 | + static auto method = |
| 60 | + ReactCallback::javaClassStatic()->getMethod<void()>("decrementPendingJSCalls"); |
| 61 | + method(jobj_); |
| 62 | + } |
| 63 | + |
| 64 | + void onNativeException(const std::string& what) override { |
| 65 | + static auto exCtor = |
| 66 | + Exception::javaClassStatic()->getConstructor<Exception::javaobject(jstring)>(); |
| 67 | + static auto method = |
| 68 | + ReactCallback::javaClassStatic()->getMethod<void(Exception::javaobject)>("onNativeException"); |
| 69 | + |
| 70 | + method(jobj_, Exception::javaClassStatic()->newObject( |
| 71 | + exCtor, jni::make_jstring(what).get()).get()); |
| 72 | + } |
| 73 | + |
| 74 | + ExecutorToken createExecutorToken() override { |
| 75 | + auto jobj = JExecutorToken::newObjectCxxArgs(); |
| 76 | + return jobj->cthis()->getExecutorToken(jobj); |
| 77 | + } |
| 78 | + |
| 79 | + void onExecutorStopped(ExecutorToken) override { |
| 80 | + // TODO(cjhopman): implement this. |
| 81 | + } |
| 82 | + |
| 83 | + private: |
| 84 | + global_ref<ReactCallback::javaobject> jobj_; |
| 85 | +}; |
| 86 | + |
| 87 | +} |
| 88 | + |
| 89 | +jni::local_ref<CatalystInstanceImpl::jhybriddata> CatalystInstanceImpl::initHybrid( |
| 90 | + jni::alias_ref<jclass>) { |
| 91 | + return makeCxxInstance(); |
| 92 | +} |
| 93 | + |
| 94 | +CatalystInstanceImpl::CatalystInstanceImpl() |
| 95 | + : instance_(folly::make_unique<Instance>()) {} |
| 96 | + |
| 97 | +void CatalystInstanceImpl::registerNatives() { |
| 98 | + registerHybrid({ |
| 99 | + makeNativeMethod("initHybrid", CatalystInstanceImpl::initHybrid), |
| 100 | + makeNativeMethod("initializeBridge", CatalystInstanceImpl::initializeBridge), |
| 101 | + makeNativeMethod("loadScriptFromAssets", |
| 102 | + "(Landroid/content/res/AssetManager;Ljava/lang/String;)V", |
| 103 | + CatalystInstanceImpl::loadScriptFromAssets), |
| 104 | + makeNativeMethod("loadScriptFromFile", CatalystInstanceImpl::loadScriptFromFile), |
| 105 | + makeNativeMethod("callJSFunction", CatalystInstanceImpl::callJSFunction), |
| 106 | + makeNativeMethod("callJSCallback", CatalystInstanceImpl::callJSCallback), |
| 107 | + makeNativeMethod("getMainExecutorToken", CatalystInstanceImpl::getMainExecutorToken), |
| 108 | + makeNativeMethod("setGlobalVariable", CatalystInstanceImpl::setGlobalVariable), |
| 109 | + makeNativeMethod("supportsProfiling", CatalystInstanceImpl::supportsProfiling), |
| 110 | + makeNativeMethod("startProfiler", CatalystInstanceImpl::startProfiler), |
| 111 | + makeNativeMethod("stopProfiler", CatalystInstanceImpl::stopProfiler), |
| 112 | + }); |
| 113 | + |
| 114 | + JNativeRunnable::registerNatives(); |
| 115 | +} |
| 116 | + |
| 117 | +void CatalystInstanceImpl::initializeBridge( |
| 118 | + jni::alias_ref<ReactCallback::javaobject> callback, |
| 119 | + // This executor is actually a factory holder. |
| 120 | + JavaScriptExecutorHolder* jseh, |
| 121 | + jni::alias_ref<JavaMessageQueueThread::javaobject> jsQueue, |
| 122 | + jni::alias_ref<JavaMessageQueueThread::javaobject> moduleQueue, |
| 123 | + ModuleRegistryHolder* mrh) { |
| 124 | + // TODO mhorowitz: how to assert here? |
| 125 | + // Assertions.assertCondition(mBridge == null, "initializeBridge should be called once"); |
| 126 | + |
| 127 | + // This used to be: |
| 128 | + // |
| 129 | + // Java CatalystInstanceImpl -> C++ CatalystInstanceImpl -> Bridge -> Bridge::Callback |
| 130 | + // --weak--> ReactCallback -> Java CatalystInstanceImpl |
| 131 | + // |
| 132 | + // Now the weak ref is a global ref. So breaking the loop depends on |
| 133 | + // CatalystInstanceImpl#destroy() calling mHybridData.resetNative(), which |
| 134 | + // should cause all the C++ pointers to be cleaned up (except C++ |
| 135 | + // CatalystInstanceImpl might be kept alive for a short time by running |
| 136 | + // callbacks). This also means that all native calls need to be pre-checked |
| 137 | + // to avoid NPE. |
| 138 | + |
| 139 | + // See the comment in callJSFunction. Once js calls switch to strings, we |
| 140 | + // don't need jsModuleDescriptions any more, all the way up and down the |
| 141 | + // stack. |
| 142 | + |
| 143 | + instance_->initializeBridge(folly::make_unique<JInstanceCallback>(callback), |
| 144 | + jseh->getExecutorFactory(), |
| 145 | + folly::make_unique<JMessageQueueThread>(jsQueue), |
| 146 | + folly::make_unique<JMessageQueueThread>(moduleQueue), |
| 147 | + mrh->getModuleRegistry()); |
| 148 | +} |
| 149 | + |
| 150 | +void CatalystInstanceImpl::loadScriptFromAssets(jobject assetManager, |
| 151 | + const std::string& assetURL) { |
| 152 | + const int kAssetsLength = 9; // strlen("assets://"); |
| 153 | + auto sourceURL = assetURL.substr(kAssetsLength); |
| 154 | + |
| 155 | + auto manager = react::extractAssetManager(assetManager); |
| 156 | + auto script = react::loadScriptFromAssets(manager, sourceURL); |
| 157 | + if (JniJSModulesUnbundle::isUnbundle(manager, sourceURL)) { |
| 158 | + instance_->loadUnbundle( |
| 159 | + folly::make_unique<JniJSModulesUnbundle>(manager, sourceURL), |
| 160 | + std::move(script), |
| 161 | + sourceURL); |
| 162 | + } else { |
| 163 | + instance_->loadScriptFromString(std::move(script), std::move(sourceURL)); |
| 164 | + } |
| 165 | +} |
| 166 | + |
| 167 | +void CatalystInstanceImpl::loadScriptFromFile(jni::alias_ref<jstring> fileName, |
| 168 | + const std::string& sourceURL) { |
| 169 | + return instance_->loadScriptFromFile(fileName ? fileName->toStdString() : "", |
| 170 | + sourceURL); |
| 171 | +} |
| 172 | + |
| 173 | +void CatalystInstanceImpl::callJSFunction( |
| 174 | + JExecutorToken* token, std::string module, std::string method, NativeArray* arguments, |
| 175 | + const std::string& tracingName) { |
| 176 | + // We want to share the C++ code, and on iOS, modules pass module/method |
| 177 | + // names as strings all the way through to JS, and there's no way to do |
| 178 | + // string -> id mapping on the objc side. So on Android, we convert the |
| 179 | + // number to a string, here which gets passed as-is to JS. There, they they |
| 180 | + // used as ids if isFinite(), which handles this case, and looked up as |
| 181 | + // strings otherwise. Eventually, we'll probably want to modify the stack |
| 182 | + // from the JS proxy through here to use strings, too. |
| 183 | + instance_->callJSFunction(token->getExecutorToken(nullptr), |
| 184 | + module, |
| 185 | + method, |
| 186 | + std::move(arguments->array), |
| 187 | + tracingName); |
| 188 | +} |
| 189 | + |
| 190 | +void CatalystInstanceImpl::callJSCallback(JExecutorToken* token, jint callbackId, NativeArray* arguments) { |
| 191 | + instance_->callJSCallback(token->getExecutorToken(nullptr), callbackId, std::move(arguments->array)); |
| 192 | +} |
| 193 | + |
| 194 | +local_ref<JExecutorToken::JavaPart> CatalystInstanceImpl::getMainExecutorToken() { |
| 195 | + return JExecutorToken::extractJavaPartFromToken(instance_->getMainExecutorToken()); |
| 196 | +} |
| 197 | + |
| 198 | +void CatalystInstanceImpl::setGlobalVariable(std::string propName, |
| 199 | + std::string&& jsonValue) { |
| 200 | + // This is only ever called from Java with short strings, and only |
| 201 | + // for testing, so no need to try hard for zero-copy here. |
| 202 | + |
| 203 | + instance_->setGlobalVariable(std::move(propName), |
| 204 | + folly::make_unique<JSBigStdString>(std::move(jsonValue))); |
| 205 | +} |
| 206 | + |
| 207 | +jboolean CatalystInstanceImpl::supportsProfiling() { |
| 208 | + if (!instance_) { |
| 209 | + return false; |
| 210 | + } |
| 211 | + return instance_->supportsProfiling(); |
| 212 | +} |
| 213 | + |
| 214 | +void CatalystInstanceImpl::startProfiler(const std::string& title) { |
| 215 | + if (!instance_) { |
| 216 | + return; |
| 217 | + } |
| 218 | + return instance_->startProfiler(title); |
| 219 | +} |
| 220 | + |
| 221 | +void CatalystInstanceImpl::stopProfiler(const std::string& title, const std::string& filename) { |
| 222 | + if (!instance_) { |
| 223 | + return; |
| 224 | + } |
| 225 | + return instance_->stopProfiler(title, filename); |
| 226 | +} |
| 227 | + |
| 228 | +}} |
0 commit comments