jiuyiUniapp/service/node_modules/react-native/ReactCommon/react/runtime/ReactInstance.cpp

689 lines
25 KiB
C++
Raw Normal View History

2025-02-13 09:59:20 +08:00
/*
* 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