689 lines
25 KiB
C++
689 lines
25 KiB
C++
|
/*
|
||
|
* 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 "ReactInstance.h"
|
||
|
|
||
|
#include <ReactCommon/RuntimeExecutor.h>
|
||
|
#include <cxxreact/ErrorUtils.h>
|
||
|
#include <cxxreact/JSBigString.h>
|
||
|
#include <cxxreact/JSExecutor.h>
|
||
|
#include <cxxreact/ReactMarker.h>
|
||
|
#include <cxxreact/SystraceSection.h>
|
||
|
#include <glog/logging.h>
|
||
|
#include <jsi/JSIDynamic.h>
|
||
|
#include <jsi/instrumentation.h>
|
||
|
#include <jsinspector-modern/HostTarget.h>
|
||
|
#include <jsireact/JSIExecutor.h>
|
||
|
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||
|
#include <react/renderer/core/ShadowNode.h>
|
||
|
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
|
||
|
#include <react/utils/jsi-utils.h>
|
||
|
#include <iostream>
|
||
|
#include <memory>
|
||
|
#include <utility>
|
||
|
|
||
|
namespace facebook::react {
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
std::shared_ptr<RuntimeScheduler> createRuntimeScheduler(
|
||
|
RuntimeExecutor runtimeExecutor,
|
||
|
RuntimeSchedulerTaskErrorHandler taskErrorHandler) {
|
||
|
std::shared_ptr<RuntimeScheduler> scheduler =
|
||
|
std::make_shared<RuntimeScheduler>(
|
||
|
runtimeExecutor, RuntimeSchedulerClock::now, taskErrorHandler);
|
||
|
scheduler->setPerformanceEntryReporter(
|
||
|
// FIXME: Move creation of PerformanceEntryReporter to here and
|
||
|
// guarantee that its lifetime is the same as the runtime.
|
||
|
PerformanceEntryReporter::getInstance().get());
|
||
|
|
||
|
return scheduler;
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
ReactInstance::ReactInstance(
|
||
|
std::unique_ptr<JSRuntime> runtime,
|
||
|
std::shared_ptr<MessageQueueThread> jsMessageQueueThread,
|
||
|
std::shared_ptr<TimerManager> timerManager,
|
||
|
JsErrorHandler::OnJsError onJsError,
|
||
|
jsinspector_modern::HostTarget* parentInspectorTarget)
|
||
|
: runtime_(std::move(runtime)),
|
||
|
jsMessageQueueThread_(jsMessageQueueThread),
|
||
|
timerManager_(std::move(timerManager)),
|
||
|
jsErrorHandler_(std::make_shared<JsErrorHandler>(std::move(onJsError))),
|
||
|
parentInspectorTarget_(parentInspectorTarget) {
|
||
|
RuntimeExecutor runtimeExecutor = [weakRuntime = std::weak_ptr(runtime_),
|
||
|
weakTimerManager =
|
||
|
std::weak_ptr(timerManager_),
|
||
|
weakJsThread =
|
||
|
std::weak_ptr(jsMessageQueueThread_),
|
||
|
jsErrorHandler =
|
||
|
jsErrorHandler_](auto callback) {
|
||
|
if (weakRuntime.expired()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* If a fatal error was caught while executing the main bundle, assume the
|
||
|
* js runtime is invalid. And stop executing any more js.
|
||
|
*/
|
||
|
if (!jsErrorHandler->isRuntimeReady() &&
|
||
|
jsErrorHandler->hasHandledFatalError()) {
|
||
|
LOG(INFO)
|
||
|
<< "RuntimeExecutor: Detected fatal error. Dropping work on non-js thread."
|
||
|
<< std::endl;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (auto jsThread = weakJsThread.lock()) {
|
||
|
jsThread->runOnQueue([jsErrorHandler,
|
||
|
weakRuntime,
|
||
|
weakTimerManager,
|
||
|
callback = std::move(callback)]() {
|
||
|
auto runtime = weakRuntime.lock();
|
||
|
if (!runtime) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
jsi::Runtime& jsiRuntime = runtime->getRuntime();
|
||
|
SystraceSection s("ReactInstance::_runtimeExecutor[Callback]");
|
||
|
try {
|
||
|
ShadowNode::setUseRuntimeShadowNodeReferenceUpdateOnThread(true);
|
||
|
callback(jsiRuntime);
|
||
|
|
||
|
// If we have first-class support for microtasks,
|
||
|
// they would've been called as part of the previous callback.
|
||
|
if (ReactNativeFeatureFlags::disableEventLoopOnBridgeless()) {
|
||
|
if (auto timerManager = weakTimerManager.lock()) {
|
||
|
timerManager->callReactNativeMicrotasks(jsiRuntime);
|
||
|
}
|
||
|
}
|
||
|
} catch (jsi::JSError& originalError) {
|
||
|
jsErrorHandler->handleError(jsiRuntime, originalError, true);
|
||
|
} catch (std::exception& ex) {
|
||
|
jsi::JSError error(
|
||
|
jsiRuntime, std::string("Non-js exception: ") + ex.what());
|
||
|
jsErrorHandler->handleError(jsiRuntime, error, true);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (parentInspectorTarget_) {
|
||
|
auto executor = parentInspectorTarget_->executorFromThis();
|
||
|
|
||
|
auto bufferedRuntimeExecutorThatWaitsForInspectorSetup =
|
||
|
std::make_shared<BufferedRuntimeExecutor>(runtimeExecutor);
|
||
|
auto runtimeExecutorThatExecutesAfterInspectorSetup =
|
||
|
[bufferedRuntimeExecutorThatWaitsForInspectorSetup](
|
||
|
std::function<void(jsi::Runtime & runtime)>&& callback) {
|
||
|
bufferedRuntimeExecutorThatWaitsForInspectorSetup->execute(
|
||
|
std::move(callback));
|
||
|
};
|
||
|
|
||
|
runtimeScheduler_ = createRuntimeScheduler(
|
||
|
runtimeExecutorThatExecutesAfterInspectorSetup,
|
||
|
[jsErrorHandler = jsErrorHandler_](
|
||
|
jsi::Runtime& runtime, jsi::JSError& error) {
|
||
|
jsErrorHandler->handleError(runtime, error, true);
|
||
|
});
|
||
|
|
||
|
auto runtimeExecutorThatGoesThroughRuntimeScheduler =
|
||
|
[runtimeScheduler = runtimeScheduler_.get()](
|
||
|
std::function<void(jsi::Runtime & runtime)>&& callback) {
|
||
|
runtimeScheduler->scheduleWork(std::move(callback));
|
||
|
};
|
||
|
|
||
|
// This code can execute from any thread, so we need to make sure we set up
|
||
|
// the inspector logic in the right one. The callback executes immediately
|
||
|
// if we are already in the right thread.
|
||
|
executor([this,
|
||
|
runtimeExecutorThatGoesThroughRuntimeScheduler,
|
||
|
bufferedRuntimeExecutorThatWaitsForInspectorSetup](
|
||
|
jsinspector_modern::HostTarget& hostTarget) {
|
||
|
// Callbacks scheduled through the page target executor are generally
|
||
|
// not guaranteed to run (e.g.: if the page target is destroyed)
|
||
|
// but in this case it is because the page target cannot be destroyed
|
||
|
// before the instance finishes its setup:
|
||
|
// * On iOS it's because we do the setup synchronously.
|
||
|
// * On Android it's because we explicitly wait for the instance
|
||
|
// creation task to finish before starting the destruction.
|
||
|
inspectorTarget_ = &hostTarget.registerInstance(*this);
|
||
|
runtimeInspectorTarget_ = &inspectorTarget_->registerRuntime(
|
||
|
runtime_->getRuntimeTargetDelegate(),
|
||
|
runtimeExecutorThatGoesThroughRuntimeScheduler);
|
||
|
bufferedRuntimeExecutorThatWaitsForInspectorSetup->flush();
|
||
|
});
|
||
|
} else {
|
||
|
runtimeScheduler_ = createRuntimeScheduler(
|
||
|
runtimeExecutor,
|
||
|
[jsErrorHandler = jsErrorHandler_](
|
||
|
jsi::Runtime& runtime, jsi::JSError& error) {
|
||
|
jsErrorHandler->handleError(runtime, error, true);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
bufferedRuntimeExecutor_ = std::make_shared<BufferedRuntimeExecutor>(
|
||
|
[runtimeScheduler = runtimeScheduler_.get()](
|
||
|
std::function<void(jsi::Runtime & runtime)>&& callback) {
|
||
|
runtimeScheduler->scheduleWork(std::move(callback));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void ReactInstance::unregisterFromInspector() {
|
||
|
if (inspectorTarget_) {
|
||
|
assert(runtimeInspectorTarget_);
|
||
|
inspectorTarget_->unregisterRuntime(*runtimeInspectorTarget_);
|
||
|
|
||
|
assert(parentInspectorTarget_);
|
||
|
parentInspectorTarget_->unregisterInstance(*inspectorTarget_);
|
||
|
|
||
|
inspectorTarget_ = nullptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RuntimeExecutor ReactInstance::getUnbufferedRuntimeExecutor() noexcept {
|
||
|
return [runtimeScheduler = runtimeScheduler_.get()](
|
||
|
std::function<void(jsi::Runtime & runtime)>&& callback) {
|
||
|
runtimeScheduler->scheduleWork(std::move(callback));
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// This BufferedRuntimeExecutor ensures that the main JS bundle finished
|
||
|
// execution before any JS queued into it from C++ are executed. Use
|
||
|
// getUnbufferedRuntimeExecutor() instead if you do not need the main JS bundle
|
||
|
// to have finished. e.g. setting global variables into JS runtime.
|
||
|
RuntimeExecutor ReactInstance::getBufferedRuntimeExecutor() noexcept {
|
||
|
return [weakBufferedRuntimeExecutor_ =
|
||
|
std::weak_ptr<BufferedRuntimeExecutor>(bufferedRuntimeExecutor_)](
|
||
|
std::function<void(jsi::Runtime & runtime)>&& callback) {
|
||
|
if (auto strongBufferedRuntimeExecutor_ =
|
||
|
weakBufferedRuntimeExecutor_.lock()) {
|
||
|
strongBufferedRuntimeExecutor_->execute(std::move(callback));
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// TODO(T184010230): Should the RuntimeScheduler returned from this method be
|
||
|
// buffered?
|
||
|
std::shared_ptr<RuntimeScheduler>
|
||
|
ReactInstance::getRuntimeScheduler() noexcept {
|
||
|
return runtimeScheduler_;
|
||
|
}
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
// Copied from JSIExecutor.cpp
|
||
|
// basename_r isn't in all iOS SDKs, so use this simple version instead.
|
||
|
std::string simpleBasename(const std::string& path) {
|
||
|
size_t pos = path.rfind("/");
|
||
|
return (pos != std::string::npos) ? path.substr(pos) : path;
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
/**
|
||
|
* Load the JS bundle and flush buffered JS calls, future JS calls won't be
|
||
|
* buffered after calling this.
|
||
|
* Note that this method is asynchronous. However, a completion callback
|
||
|
* isn't needed because all calls into JS should be dispatched to the JSThread,
|
||
|
* preferably via the runtimeExecutor_.
|
||
|
*/
|
||
|
void ReactInstance::loadScript(
|
||
|
std::unique_ptr<const JSBigString> script,
|
||
|
const std::string& sourceURL,
|
||
|
std::function<void(jsi::Runtime& runtime)>&& completion) {
|
||
|
auto buffer = std::make_shared<BigStringBuffer>(std::move(script));
|
||
|
std::string scriptName = simpleBasename(sourceURL);
|
||
|
|
||
|
runtimeScheduler_->scheduleWork([this,
|
||
|
scriptName,
|
||
|
sourceURL,
|
||
|
buffer = std::move(buffer),
|
||
|
weakBufferedRuntimeExecuter =
|
||
|
std::weak_ptr<BufferedRuntimeExecutor>(
|
||
|
bufferedRuntimeExecutor_),
|
||
|
completion](jsi::Runtime& runtime) {
|
||
|
SystraceSection s("ReactInstance::loadScript");
|
||
|
bool hasLogger(ReactMarker::logTaggedMarkerBridgelessImpl);
|
||
|
if (hasLogger) {
|
||
|
ReactMarker::logTaggedMarkerBridgeless(
|
||
|
ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
|
||
|
}
|
||
|
|
||
|
runtime.evaluateJavaScript(buffer, sourceURL);
|
||
|
|
||
|
/**
|
||
|
* TODO(T183610671): We need a safe/reliable way to enable the js
|
||
|
* pipeline from javascript. Remove this after we figure that out, or
|
||
|
* after we just remove the js pipeline.
|
||
|
*/
|
||
|
if (!jsErrorHandler_->hasHandledFatalError()) {
|
||
|
jsErrorHandler_->setRuntimeReady();
|
||
|
}
|
||
|
|
||
|
if (hasLogger) {
|
||
|
ReactMarker::logTaggedMarkerBridgeless(
|
||
|
ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
|
||
|
ReactMarker::logMarkerBridgeless(ReactMarker::INIT_REACT_RUNTIME_STOP);
|
||
|
ReactMarker::logMarkerBridgeless(ReactMarker::APP_STARTUP_STOP);
|
||
|
}
|
||
|
if (auto strongBufferedRuntimeExecuter =
|
||
|
weakBufferedRuntimeExecuter.lock()) {
|
||
|
strongBufferedRuntimeExecuter->flush();
|
||
|
}
|
||
|
if (completion) {
|
||
|
completion(runtime);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Calls a method on a JS module that has been registered with
|
||
|
* `registerCallableModule`. Used to invoke a JS function from platform code.
|
||
|
*/
|
||
|
void ReactInstance::callFunctionOnModule(
|
||
|
const std::string& moduleName,
|
||
|
const std::string& methodName,
|
||
|
folly::dynamic&& args) {
|
||
|
if (bufferedRuntimeExecutor_ == nullptr) {
|
||
|
LOG(ERROR)
|
||
|
<< "Calling callFunctionOnModule with null BufferedRuntimeExecutor";
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
bufferedRuntimeExecutor_->execute([this,
|
||
|
moduleName = moduleName,
|
||
|
methodName = methodName,
|
||
|
args = std::move(args)](
|
||
|
jsi::Runtime& runtime) {
|
||
|
SystraceSection s(
|
||
|
"ReactInstance::callFunctionOnModule",
|
||
|
"moduleName",
|
||
|
moduleName,
|
||
|
"methodName",
|
||
|
methodName);
|
||
|
auto it = callableModules_.find(moduleName);
|
||
|
if (it == callableModules_.end()) {
|
||
|
std::ostringstream knownModules;
|
||
|
int i = 0;
|
||
|
for (it = callableModules_.begin(); it != callableModules_.end();
|
||
|
it++, i++) {
|
||
|
const char* space = (i > 0 ? ", " : " ");
|
||
|
knownModules << space << it->first;
|
||
|
}
|
||
|
throw jsi::JSError(
|
||
|
runtime,
|
||
|
"Failed to call into JavaScript module method " + moduleName + "." +
|
||
|
methodName +
|
||
|
"(). Module has not been registered as callable. Registered callable JavaScript modules (n = " +
|
||
|
std::to_string(callableModules_.size()) +
|
||
|
"):" + knownModules.str() +
|
||
|
". Did you forget to call `registerCallableModule`?");
|
||
|
}
|
||
|
|
||
|
if (std::holds_alternative<jsi::Function>(it->second)) {
|
||
|
auto module =
|
||
|
std::get<jsi::Function>(it->second).call(runtime).asObject(runtime);
|
||
|
it->second = std::move(module);
|
||
|
}
|
||
|
|
||
|
auto& module = std::get<jsi::Object>(it->second);
|
||
|
auto method = module.getPropertyAsFunction(runtime, methodName.c_str());
|
||
|
|
||
|
std::vector<jsi::Value> jsArgs;
|
||
|
for (auto& arg : args) {
|
||
|
jsArgs.push_back(jsi::valueFromDynamic(runtime, arg));
|
||
|
}
|
||
|
method.callWithThis(
|
||
|
runtime, module, (const jsi::Value*)jsArgs.data(), jsArgs.size());
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void ReactInstance::registerSegment(
|
||
|
uint32_t segmentId,
|
||
|
const std::string& segmentPath) {
|
||
|
LOG(WARNING) << "Starting to run ReactInstance::registerSegment with segment "
|
||
|
<< segmentId;
|
||
|
runtimeScheduler_->scheduleWork([=](jsi::Runtime& runtime) {
|
||
|
SystraceSection s("ReactInstance::registerSegment");
|
||
|
const auto tag = folly::to<std::string>(segmentId);
|
||
|
auto script = JSBigFileString::fromPath(segmentPath);
|
||
|
if (script->size() == 0) {
|
||
|
throw std::invalid_argument(
|
||
|
"Empty segment registered with ID " + tag + " from " + segmentPath);
|
||
|
}
|
||
|
auto buffer = std::make_shared<BigStringBuffer>(std::move(script));
|
||
|
|
||
|
bool hasLogger(ReactMarker::logTaggedMarkerBridgelessImpl);
|
||
|
if (hasLogger) {
|
||
|
ReactMarker::logTaggedMarkerBridgeless(
|
||
|
ReactMarker::REGISTER_JS_SEGMENT_START, tag.c_str());
|
||
|
}
|
||
|
LOG(WARNING) << "Starting to evaluate segment " << segmentId
|
||
|
<< " in ReactInstance::registerSegment";
|
||
|
runtime.evaluateJavaScript(
|
||
|
buffer, JSExecutor::getSyntheticBundlePath(segmentId, segmentPath));
|
||
|
LOG(WARNING) << "Finished evaluating segment " << segmentId
|
||
|
<< " in ReactInstance::registerSegment";
|
||
|
if (hasLogger) {
|
||
|
ReactMarker::logTaggedMarkerBridgeless(
|
||
|
ReactMarker::REGISTER_JS_SEGMENT_STOP, tag.c_str());
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
namespace {
|
||
|
void defineReactInstanceFlags(
|
||
|
jsi::Runtime& runtime,
|
||
|
ReactInstance::JSRuntimeFlags options) noexcept {
|
||
|
defineReadOnlyGlobal(runtime, "RN$Bridgeless", jsi::Value(true));
|
||
|
|
||
|
if (options.isProfiling) {
|
||
|
defineReadOnlyGlobal(runtime, "__RCTProfileIsProfiling", jsi::Value(true));
|
||
|
}
|
||
|
|
||
|
if (options.runtimeDiagnosticFlags.length() > 0) {
|
||
|
defineReadOnlyGlobal(
|
||
|
runtime,
|
||
|
"RN$DiagnosticFlags",
|
||
|
jsi::String::createFromUtf8(runtime, options.runtimeDiagnosticFlags));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool isTruthy(jsi::Runtime& runtime, const jsi::Value& value) {
|
||
|
auto Boolean = runtime.global().getPropertyAsFunction(runtime, "Boolean");
|
||
|
return Boolean.call(runtime, value).getBool();
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
void ReactInstance::initializeRuntime(
|
||
|
JSRuntimeFlags options,
|
||
|
BindingsInstallFunc bindingsInstallFunc) noexcept {
|
||
|
runtimeScheduler_->scheduleWork([this, options, bindingsInstallFunc](
|
||
|
jsi::Runtime& runtime) {
|
||
|
SystraceSection s("ReactInstance::initializeRuntime");
|
||
|
|
||
|
bindNativePerformanceNow(runtime);
|
||
|
|
||
|
RuntimeSchedulerBinding::createAndInstallIfNeeded(
|
||
|
runtime, runtimeScheduler_);
|
||
|
|
||
|
runtime_->unstable_initializeOnJsThread();
|
||
|
|
||
|
defineReactInstanceFlags(runtime, options);
|
||
|
|
||
|
defineReadOnlyGlobal(
|
||
|
runtime,
|
||
|
"RN$useAlwaysAvailableJSErrorHandling",
|
||
|
jsi::Value(
|
||
|
ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling()));
|
||
|
|
||
|
defineReadOnlyGlobal(
|
||
|
runtime,
|
||
|
"RN$isRuntimeReady",
|
||
|
jsi::Function::createFromHostFunction(
|
||
|
runtime,
|
||
|
jsi::PropNameID::forAscii(runtime, "isRuntimeReady"),
|
||
|
0,
|
||
|
[jsErrorHandler = jsErrorHandler_](
|
||
|
jsi::Runtime& /*runtime*/,
|
||
|
const jsi::Value& /*unused*/,
|
||
|
const jsi::Value* /*args*/,
|
||
|
size_t /*count*/) {
|
||
|
return jsErrorHandler->isRuntimeReady();
|
||
|
}));
|
||
|
|
||
|
defineReadOnlyGlobal(
|
||
|
runtime,
|
||
|
"RN$hasHandledFatalException",
|
||
|
jsi::Function::createFromHostFunction(
|
||
|
runtime,
|
||
|
jsi::PropNameID::forAscii(runtime, "hasHandledFatalException"),
|
||
|
0,
|
||
|
[jsErrorHandler = jsErrorHandler_](
|
||
|
jsi::Runtime& /*runtime*/,
|
||
|
const jsi::Value& /*unused*/,
|
||
|
const jsi::Value* /*args*/,
|
||
|
size_t /*count*/) {
|
||
|
return jsErrorHandler->hasHandledFatalError();
|
||
|
}));
|
||
|
|
||
|
defineReadOnlyGlobal(
|
||
|
runtime,
|
||
|
"RN$notifyOfFatalException",
|
||
|
jsi::Function::createFromHostFunction(
|
||
|
runtime,
|
||
|
jsi::PropNameID::forAscii(runtime, "notifyOfFatalException"),
|
||
|
0,
|
||
|
[jsErrorHandler = jsErrorHandler_](
|
||
|
jsi::Runtime& /*runtime*/,
|
||
|
const jsi::Value& /*unused*/,
|
||
|
const jsi::Value* /*args*/,
|
||
|
size_t /*count*/) {
|
||
|
jsErrorHandler->notifyOfFatalError();
|
||
|
return jsi::Value::undefined();
|
||
|
}));
|
||
|
|
||
|
defineReadOnlyGlobal(
|
||
|
runtime,
|
||
|
"RN$inExceptionHandler",
|
||
|
jsi::Function::createFromHostFunction(
|
||
|
runtime,
|
||
|
jsi::PropNameID::forAscii(runtime, "inExceptionHandler"),
|
||
|
0,
|
||
|
[jsErrorHandler = jsErrorHandler_](
|
||
|
jsi::Runtime& /*runtime*/,
|
||
|
const jsi::Value& /*unused*/,
|
||
|
const jsi::Value* /*args*/,
|
||
|
size_t /*count*/) {
|
||
|
return jsErrorHandler->inErrorHandler();
|
||
|
}));
|
||
|
|
||
|
// TODO(T196834299): We should really use a C++ turbomodule for this
|
||
|
defineReadOnlyGlobal(
|
||
|
runtime,
|
||
|
"RN$handleException",
|
||
|
jsi::Function::createFromHostFunction(
|
||
|
runtime,
|
||
|
jsi::PropNameID::forAscii(runtime, "handleException"),
|
||
|
3,
|
||
|
[jsErrorHandler = jsErrorHandler_](
|
||
|
jsi::Runtime& runtime,
|
||
|
const jsi::Value& /*unused*/,
|
||
|
const jsi::Value* args,
|
||
|
size_t count) {
|
||
|
if (count < 2) {
|
||
|
throw jsi::JSError(
|
||
|
runtime,
|
||
|
"handleException requires 3 arguments: error, isFatal, logToConsole (optional)");
|
||
|
}
|
||
|
|
||
|
auto isFatal = isTruthy(runtime, args[1]);
|
||
|
|
||
|
if (!ReactNativeFeatureFlags::
|
||
|
useAlwaysAvailableJSErrorHandling()) {
|
||
|
if (jsErrorHandler->isRuntimeReady()) {
|
||
|
return jsi::Value(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
auto jsError =
|
||
|
jsi::JSError(runtime, jsi::Value(runtime, args[0]));
|
||
|
|
||
|
if (count == 2) {
|
||
|
jsErrorHandler->handleError(runtime, jsError, isFatal);
|
||
|
} else {
|
||
|
auto logToConsole = isTruthy(runtime, args[2]);
|
||
|
jsErrorHandler->handleError(
|
||
|
runtime, jsError, isFatal, logToConsole);
|
||
|
}
|
||
|
|
||
|
return jsi::Value(true);
|
||
|
}));
|
||
|
|
||
|
defineReadOnlyGlobal(
|
||
|
runtime,
|
||
|
"RN$registerExceptionListener",
|
||
|
jsi::Function::createFromHostFunction(
|
||
|
runtime,
|
||
|
jsi::PropNameID::forAscii(runtime, "registerExceptionListener"),
|
||
|
1,
|
||
|
[errorListeners = std::vector<std::shared_ptr<jsi::Function>>(),
|
||
|
jsErrorHandler = jsErrorHandler_](
|
||
|
jsi::Runtime& runtime,
|
||
|
const jsi::Value& /*unused*/,
|
||
|
const jsi::Value* args,
|
||
|
size_t count) mutable {
|
||
|
if (count < 1) {
|
||
|
throw jsi::JSError(
|
||
|
runtime,
|
||
|
"registerExceptionListener: requires 1 argument: fn");
|
||
|
}
|
||
|
|
||
|
if (!args[0].isObject() ||
|
||
|
!args[0].getObject(runtime).isFunction(runtime)) {
|
||
|
throw jsi::JSError(
|
||
|
runtime,
|
||
|
"registerExceptionListener: The first argument must be a function");
|
||
|
}
|
||
|
|
||
|
auto errorListener = std::make_shared<jsi::Function>(
|
||
|
args[0].getObject(runtime).getFunction(runtime));
|
||
|
errorListeners.emplace_back(errorListener);
|
||
|
|
||
|
jsErrorHandler->registerErrorListener(
|
||
|
[weakErrorListener = std::weak_ptr<jsi::Function>(
|
||
|
errorListener)](jsi::Runtime& runtime, jsi::Value data) {
|
||
|
if (auto strongErrorListener = weakErrorListener.lock()) {
|
||
|
strongErrorListener->call(runtime, data);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return jsi::Value::undefined();
|
||
|
}));
|
||
|
|
||
|
defineReadOnlyGlobal(
|
||
|
runtime,
|
||
|
"RN$registerCallableModule",
|
||
|
jsi::Function::createFromHostFunction(
|
||
|
runtime,
|
||
|
jsi::PropNameID::forAscii(runtime, "registerCallableModule"),
|
||
|
2,
|
||
|
[this](
|
||
|
jsi::Runtime& runtime,
|
||
|
const jsi::Value& /*unused*/,
|
||
|
const jsi::Value* args,
|
||
|
size_t count) {
|
||
|
if (count != 2) {
|
||
|
throw jsi::JSError(
|
||
|
runtime,
|
||
|
"registerCallableModule requires exactly 2 arguments");
|
||
|
}
|
||
|
if (!args[0].isString()) {
|
||
|
throw jsi::JSError(
|
||
|
runtime,
|
||
|
"The first argument to registerCallableModule must be a string (the name of the JS module).");
|
||
|
}
|
||
|
auto name = args[0].asString(runtime).utf8(runtime);
|
||
|
if (!args[1].isObject() ||
|
||
|
!args[1].getObject(runtime).isFunction(runtime)) {
|
||
|
throw jsi::JSError(
|
||
|
runtime,
|
||
|
"The second argument to registerCallableModule must be a function that returns the JS module.");
|
||
|
}
|
||
|
callableModules_.emplace(
|
||
|
std::move(name),
|
||
|
args[1].getObject(runtime).getFunction(runtime));
|
||
|
return jsi::Value::undefined();
|
||
|
}));
|
||
|
|
||
|
timerManager_->attachGlobals(runtime);
|
||
|
|
||
|
bindingsInstallFunc(runtime);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void ReactInstance::handleMemoryPressureJs(int pressureLevel) {
|
||
|
// The level is an enum value passed by the Android OS to an onTrimMemory
|
||
|
// event callback. Defined in ComponentCallbacks2.
|
||
|
enum AndroidMemoryPressure {
|
||
|
TRIM_MEMORY_BACKGROUND = 40,
|
||
|
TRIM_MEMORY_COMPLETE = 80,
|
||
|
TRIM_MEMORY_MODERATE = 60,
|
||
|
TRIM_MEMORY_RUNNING_CRITICAL = 15,
|
||
|
TRIM_MEMORY_RUNNING_LOW = 10,
|
||
|
TRIM_MEMORY_RUNNING_MODERATE = 5,
|
||
|
TRIM_MEMORY_UI_HIDDEN = 20,
|
||
|
};
|
||
|
const char* levelName;
|
||
|
switch (pressureLevel) {
|
||
|
case TRIM_MEMORY_BACKGROUND:
|
||
|
levelName = "TRIM_MEMORY_BACKGROUND";
|
||
|
break;
|
||
|
case TRIM_MEMORY_COMPLETE:
|
||
|
levelName = "TRIM_MEMORY_COMPLETE";
|
||
|
break;
|
||
|
case TRIM_MEMORY_MODERATE:
|
||
|
levelName = "TRIM_MEMORY_MODERATE";
|
||
|
break;
|
||
|
case TRIM_MEMORY_RUNNING_CRITICAL:
|
||
|
levelName = "TRIM_MEMORY_RUNNING_CRITICAL";
|
||
|
break;
|
||
|
case TRIM_MEMORY_RUNNING_LOW:
|
||
|
levelName = "TRIM_MEMORY_RUNNING_LOW";
|
||
|
break;
|
||
|
case TRIM_MEMORY_RUNNING_MODERATE:
|
||
|
levelName = "TRIM_MEMORY_RUNNING_MODERATE";
|
||
|
break;
|
||
|
case TRIM_MEMORY_UI_HIDDEN:
|
||
|
levelName = "TRIM_MEMORY_UI_HIDDEN";
|
||
|
break;
|
||
|
default:
|
||
|
levelName = "UNKNOWN";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (pressureLevel) {
|
||
|
case TRIM_MEMORY_RUNNING_LOW:
|
||
|
case TRIM_MEMORY_RUNNING_MODERATE:
|
||
|
case TRIM_MEMORY_UI_HIDDEN:
|
||
|
// For non-severe memory trims, do nothing.
|
||
|
LOG(INFO) << "Memory warning (pressure level: " << levelName
|
||
|
<< ") received by JS VM, ignoring because it's non-severe";
|
||
|
break;
|
||
|
case TRIM_MEMORY_BACKGROUND:
|
||
|
case TRIM_MEMORY_COMPLETE:
|
||
|
case TRIM_MEMORY_MODERATE:
|
||
|
case TRIM_MEMORY_RUNNING_CRITICAL:
|
||
|
// For now, pressureLevel is unused by collectGarbage.
|
||
|
// This may change in the future if the JS GC has different styles of
|
||
|
// collections.
|
||
|
LOG(INFO) << "Memory warning (pressure level: " << levelName
|
||
|
<< ") received by JS VM, running a GC";
|
||
|
runtimeScheduler_->scheduleWork([=](jsi::Runtime& runtime) {
|
||
|
SystraceSection s("ReactInstance::handleMemoryPressure");
|
||
|
runtime.instrumentation().collectGarbage(levelName);
|
||
|
});
|
||
|
break;
|
||
|
default:
|
||
|
// Use the raw number instead of the name here since the name is
|
||
|
// meaningless.
|
||
|
LOG(WARNING) << "Memory warning (pressure level: " << pressureLevel
|
||
|
<< ") received by JS VM, unrecognized pressure level";
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void* ReactInstance::getJavaScriptContext() {
|
||
|
return &runtime_->getRuntime();
|
||
|
}
|
||
|
|
||
|
} // namespace facebook::react
|