1 /*
2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE file in the root directory of this source tree.
6  */
7 
8 #include "ABI47_0_0ModuleRegistry.h"
9 
10 #include <glog/logging.h>
11 #include <ABI47_0_0Reactperflogger/ABI47_0_0BridgeNativeModulePerfLogger.h>
12 
13 #include "ABI47_0_0NativeModule.h"
14 #include "ABI47_0_0SystraceSection.h"
15 
16 namespace ABI47_0_0facebook {
17 namespace ABI47_0_0React {
18 
19 namespace {
20 
normalizeName(std::string name)21 std::string normalizeName(std::string name) {
22   if (name.compare(0, 9, "ABI47_0_0") == 0) {
23     name = name.substr(9);
24   }
25 
26   // TODO mhorowitz #10487027: This is super ugly.  We should just
27   // change iOS to emit normalized names, drop the "RK..." from
28   // names hardcoded in Android, and then delete this and the
29   // similar hacks in js.
30   if (name.compare(0, 3, "RCT") == 0) {
31     return name.substr(3);
32   } else if (name.compare(0, 2, "RK") == 0) {
33     return name.substr(2);
34   }
35   return name;
36 }
37 
38 } // namespace
39 
ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules,ModuleNotFoundCallback callback)40 ModuleRegistry::ModuleRegistry(
41     std::vector<std::unique_ptr<NativeModule>> modules,
42     ModuleNotFoundCallback callback)
43     : modules_{std::move(modules)}, moduleNotFoundCallback_{callback} {}
44 
updateModuleNamesFromIndex(size_t index)45 void ModuleRegistry::updateModuleNamesFromIndex(size_t index) {
46   for (; index < modules_.size(); index++) {
47     std::string name = normalizeName(modules_[index]->getName());
48     modulesByName_[name] = index;
49   }
50 }
51 
registerModules(std::vector<std::unique_ptr<NativeModule>> modules)52 void ModuleRegistry::registerModules(
53     std::vector<std::unique_ptr<NativeModule>> modules) {
54   SystraceSection s_("ModuleRegistry::registerModules");
55   // Noop if there are no NativeModules to add
56   if (modules.empty()) {
57     return;
58   }
59 
60   if (modules_.empty() && unknownModules_.empty()) {
61     modules_ = std::move(modules);
62   } else {
63     size_t modulesSize = modules_.size();
64     size_t addModulesSize = modules.size();
65     bool addToNames = !modulesByName_.empty();
66     modules_.reserve(modulesSize + addModulesSize);
67     std::move(modules.begin(), modules.end(), std::back_inserter(modules_));
68     if (!unknownModules_.empty()) {
69       for (size_t index = modulesSize; index < modulesSize + addModulesSize;
70            index++) {
71         std::string name = normalizeName(modules_[index]->getName());
72         auto it = unknownModules_.find(name);
73         if (it != unknownModules_.end()) {
74           throw std::runtime_error(folly::to<std::string>(
75               "module ",
76               name,
77               " was required without being registered and is now being registered."));
78         } else if (addToNames) {
79           modulesByName_[name] = index;
80         }
81       }
82     } else if (addToNames) {
83       updateModuleNamesFromIndex(modulesSize);
84     }
85   }
86 }
87 
moduleNames()88 std::vector<std::string> ModuleRegistry::moduleNames() {
89   SystraceSection s_("ModuleRegistry::moduleNames");
90   std::vector<std::string> names;
91   for (size_t i = 0; i < modules_.size(); i++) {
92     std::string name = normalizeName(modules_[i]->getName());
93     modulesByName_[name] = i;
94     names.push_back(std::move(name));
95   }
96   return names;
97 }
98 
getConfig(const std::string & name)99 folly::Optional<ModuleConfig> ModuleRegistry::getConfig(
100     const std::string &name) {
101   SystraceSection s("ModuleRegistry::getConfig", "module", name);
102 
103   // Initialize modulesByName_
104   if (modulesByName_.empty() && !modules_.empty()) {
105     moduleNames();
106   }
107 
108   auto it = modulesByName_.find(name);
109 
110   if (it == modulesByName_.end()) {
111     if (unknownModules_.find(name) != unknownModules_.end()) {
112       BridgeNativeModulePerfLogger::moduleJSRequireBeginningFail(name.c_str());
113       BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
114       return folly::none;
115     }
116 
117     if (!moduleNotFoundCallback_) {
118       unknownModules_.insert(name);
119       BridgeNativeModulePerfLogger::moduleJSRequireBeginningFail(name.c_str());
120       BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
121       return folly::none;
122     }
123 
124     BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd(name.c_str());
125 
126     bool wasModuleLazilyLoaded = moduleNotFoundCallback_(name);
127     it = modulesByName_.find(name);
128 
129     bool wasModuleRegisteredWithRegistry =
130         wasModuleLazilyLoaded && it != modulesByName_.end();
131 
132     if (!wasModuleRegisteredWithRegistry) {
133       BridgeNativeModulePerfLogger::moduleJSRequireEndingStart(name.c_str());
134       unknownModules_.insert(name);
135       return folly::none;
136     }
137   } else {
138     BridgeNativeModulePerfLogger::moduleJSRequireBeginningEnd(name.c_str());
139   }
140 
141   // If we've gotten this far, then we've signaled moduleJSRequireBeginningEnd
142 
143   size_t index = it->second;
144 
145   CHECK(index < modules_.size());
146   NativeModule *module = modules_[index].get();
147 
148   // string name, object constants, array methodNames (methodId is index),
149   // [array promiseMethodIds], [array syncMethodIds]
150   folly::dynamic config = folly::dynamic::array(name);
151 
152   {
153     SystraceSection s_("ModuleRegistry::getConstants", "module", name);
154     /**
155      * In the case that there are constants, we'll initialize the NativeModule,
156      * and signal moduleJSRequireEndingStart. Otherwise, we'll simply signal the
157      * event. The Module will be initialized when we invoke one of its
158      * NativeModule methods.
159      */
160     config.push_back(module->getConstants());
161   }
162 
163   {
164     SystraceSection s_("ModuleRegistry::getMethods", "module", name);
165     std::vector<MethodDescriptor> methods = module->getMethods();
166 
167     folly::dynamic methodNames = folly::dynamic::array;
168     folly::dynamic promiseMethodIds = folly::dynamic::array;
169     folly::dynamic syncMethodIds = folly::dynamic::array;
170 
171     for (auto &descriptor : methods) {
172       // TODO: #10487027 compare tags instead of doing string comparison?
173       methodNames.push_back(std::move(descriptor.name));
174       if (descriptor.type == "promise") {
175         promiseMethodIds.push_back(methodNames.size() - 1);
176       } else if (descriptor.type == "sync") {
177         syncMethodIds.push_back(methodNames.size() - 1);
178       }
179     }
180 
181     if (!methodNames.empty()) {
182       config.push_back(std::move(methodNames));
183       if (!promiseMethodIds.empty() || !syncMethodIds.empty()) {
184         config.push_back(std::move(promiseMethodIds));
185         if (!syncMethodIds.empty()) {
186           config.push_back(std::move(syncMethodIds));
187         }
188       }
189     }
190   }
191 
192   if (config.size() == 2 && config[1].empty()) {
193     // no constants or methods
194     return folly::none;
195   } else {
196     return ModuleConfig{index, std::move(config)};
197   }
198 }
199 
getModuleName(unsigned int moduleId)200 std::string ModuleRegistry::getModuleName(unsigned int moduleId) {
201   if (moduleId >= modules_.size()) {
202     throw std::runtime_error(folly::to<std::string>(
203         "moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
204   }
205 
206   return modules_[moduleId]->getName();
207 }
208 
getModuleSyncMethodName(unsigned int moduleId,unsigned int methodId)209 std::string ModuleRegistry::getModuleSyncMethodName(
210     unsigned int moduleId,
211     unsigned int methodId) {
212   if (moduleId >= modules_.size()) {
213     throw std::runtime_error(folly::to<std::string>(
214         "moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
215   }
216 
217   return modules_[moduleId]->getSyncMethodName(methodId);
218 }
219 
callNativeMethod(unsigned int moduleId,unsigned int methodId,folly::dynamic && params,int callId)220 void ModuleRegistry::callNativeMethod(
221     unsigned int moduleId,
222     unsigned int methodId,
223     folly::dynamic &&params,
224     int callId) {
225   if (moduleId >= modules_.size()) {
226     throw std::runtime_error(folly::to<std::string>(
227         "moduleId ", moduleId, " out of range [0..", modules_.size(), ")"));
228   }
229   modules_[moduleId]->invoke(methodId, std::move(params), callId);
230 }
231 
callSerializableNativeHook(unsigned int moduleId,unsigned int methodId,folly::dynamic && params)232 MethodCallResult ModuleRegistry::callSerializableNativeHook(
233     unsigned int moduleId,
234     unsigned int methodId,
235     folly::dynamic &&params) {
236   if (moduleId >= modules_.size()) {
237     throw std::runtime_error(folly::to<std::string>(
238         "moduleId ", moduleId, "out of range [0..", modules_.size(), ")"));
239   }
240   return modules_[moduleId]->callSerializableNativeHook(
241       methodId, std::move(params));
242 }
243 
244 } // namespace ABI47_0_0React
245 } // namespace ABI47_0_0facebook
246