Move C++ code to OSS folders (#36789)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/36789 Move > xplat/ReactNative/venice to > xplat/js/react-native-github/packages/react-native/ReactCommon/react/bridgeless Changelog: [General][Changed] - Move Bridgeless C++ code to OSS folders Reviewed By: fkgozali Differential Revision: D44626153 fbshipit-source-id: ec8340db92b805d07d3c5f8e86cb35335637ccd6
This commit is contained in:
Родитель
94356e14ec
Коммит
e2e59c4d0e
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
InheritParentConfig: true
|
||||
Checks: '
|
||||
-cert-err60-cpp,
|
||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
||||
-cppcoreguidelines-special-member-functions,
|
||||
-fuchsia-default-arguments-calls,
|
||||
-google-readability-casting,
|
||||
-google-runtime-references,
|
||||
-hicpp-special-member-functions,
|
||||
-llvm-header-guard,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-misc-unused-parameters,
|
||||
-modernize-use-trailing-return-type,
|
||||
-performance-unnecessary-value-param
|
||||
'
|
||||
...
|
|
@ -0,0 +1,57 @@
|
|||
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
|
||||
#include "BufferedRuntimeExecutor.h"
|
||||
#include <cxxreact/MessageQueueThread.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
BufferedRuntimeExecutor::BufferedRuntimeExecutor(
|
||||
RuntimeExecutor runtimeExecutor)
|
||||
: runtimeExecutor_(runtimeExecutor),
|
||||
isBufferingEnabled_(true),
|
||||
lastIndex_(0) {}
|
||||
|
||||
void BufferedRuntimeExecutor::execute(Work &&callback) {
|
||||
if (!isBufferingEnabled_) {
|
||||
// Fast path: Schedule directly to RuntimeExecutor, without locking
|
||||
runtimeExecutor_(std::move(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: std::mutex doesn't have a FIFO ordering.
|
||||
* To preserve the order of the buffered work, use a priority queue and
|
||||
* track the last known work index.
|
||||
*/
|
||||
uint64_t newIndex = lastIndex_++;
|
||||
std::lock_guard<std::mutex> guard(lock_);
|
||||
if (isBufferingEnabled_) {
|
||||
queue_.push({.index_ = newIndex, .work_ = std::move(callback)});
|
||||
return;
|
||||
}
|
||||
|
||||
// Force flush the queue to maintain the execution order.
|
||||
unsafeFlush();
|
||||
|
||||
runtimeExecutor_(std::move(callback));
|
||||
}
|
||||
|
||||
void BufferedRuntimeExecutor::flush() {
|
||||
std::lock_guard<std::mutex> guard(lock_);
|
||||
unsafeFlush();
|
||||
isBufferingEnabled_ = false;
|
||||
}
|
||||
|
||||
void BufferedRuntimeExecutor::unsafeFlush() {
|
||||
while (queue_.size() > 0) {
|
||||
BufferedWork const &bufferedWork = queue_.top();
|
||||
Work work = std::move(bufferedWork.work_);
|
||||
runtimeExecutor_(std::move(work));
|
||||
queue_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,46 @@
|
|||
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
|
||||
#include <ReactCommon/RuntimeExecutor.h>
|
||||
#include <jsi/jsi.h>
|
||||
#include <react/bridgeless/TimerManager.h>
|
||||
#include <atomic>
|
||||
#include <queue>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class BufferedRuntimeExecutor {
|
||||
public:
|
||||
using Work = std::function<void(jsi::Runtime &runtime)>;
|
||||
|
||||
// A utility structure to track pending work in the order of when they arrive.
|
||||
struct BufferedWork {
|
||||
uint64_t index_;
|
||||
Work work_;
|
||||
bool operator<(const BufferedWork &rhs) const {
|
||||
// Higher index has lower priority, so this inverted comparison puts
|
||||
// the smaller index on top of the queue.
|
||||
return index_ > rhs.index_;
|
||||
}
|
||||
};
|
||||
|
||||
BufferedRuntimeExecutor(RuntimeExecutor runtimeExecutor);
|
||||
|
||||
void execute(Work &&callback);
|
||||
|
||||
// Flush buffered JS calls and then diable JS buffering
|
||||
void flush();
|
||||
|
||||
private:
|
||||
// Perform flushing without locking mechanism
|
||||
void unsafeFlush();
|
||||
|
||||
RuntimeExecutor runtimeExecutor_;
|
||||
bool isBufferingEnabled_;
|
||||
std::mutex lock_;
|
||||
std::atomic<uint64_t> lastIndex_;
|
||||
std::priority_queue<BufferedWork> queue_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,22 @@
|
|||
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ReactCommon/RuntimeExecutor.h>
|
||||
#include <jsi/jsi.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/**
|
||||
* Interface for a class that creates and owns an instance of a JS VM
|
||||
*/
|
||||
class JSEngineInstance {
|
||||
public:
|
||||
virtual std::unique_ptr<jsi::Runtime> createJSRuntime() noexcept = 0;
|
||||
|
||||
virtual ~JSEngineInstance() = default;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,29 @@
|
|||
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/**
|
||||
* This interface is implemented by each platform.
|
||||
* Responsibility: Call into some platform API to register/schedule, or delete
|
||||
* registered/scheduled timers.
|
||||
*/
|
||||
class PlatformTimerRegistry {
|
||||
public:
|
||||
virtual void createTimer(uint32_t timerID, double delayMS) = 0;
|
||||
|
||||
virtual void deleteTimer(uint32_t timerID) = 0;
|
||||
|
||||
virtual void createRecurringTimer(uint32_t timerID, double delayMS) = 0;
|
||||
|
||||
virtual ~PlatformTimerRegistry() noexcept = default;
|
||||
};
|
||||
|
||||
using TimerManagerDelegate = PlatformTimerRegistry;
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,453 @@
|
|||
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
|
||||
#include "ReactInstance.h"
|
||||
|
||||
#include <cxxreact/ErrorUtils.h>
|
||||
#include <cxxreact/JSBigString.h>
|
||||
#include <cxxreact/SystraceSection.h>
|
||||
#include <glog/logging.h>
|
||||
#include <jsi/instrumentation.h>
|
||||
#include <jsi/jsi/JSIDynamic.h>
|
||||
#include <jsireact/JSIExecutor.h>
|
||||
#include <jsireact/JSITracing.h>
|
||||
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
|
||||
|
||||
#include <cxxreact/ReactMarker.h>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
// Looping on \c drainMicrotasks until it completes or hits the retries bound.
|
||||
static void performMicrotaskCheckpoint(jsi::Runtime &runtime) {
|
||||
uint8_t retries = 0;
|
||||
// A heuristic number to guard inifinite or absurd numbers of retries.
|
||||
constexpr unsigned int kRetriesBound = 255;
|
||||
|
||||
while (retries < kRetriesBound) {
|
||||
try {
|
||||
// The default behavior of \c drainMicrotasks is unbounded execution.
|
||||
// We may want to make it bounded in the future.
|
||||
if (runtime.drainMicrotasks()) {
|
||||
break;
|
||||
}
|
||||
} catch (jsi::JSError &error) {
|
||||
handleJSError(runtime, error, true);
|
||||
}
|
||||
retries++;
|
||||
}
|
||||
|
||||
if (retries == kRetriesBound) {
|
||||
throw std::runtime_error("Hits microtasks retries bound.");
|
||||
}
|
||||
}
|
||||
|
||||
ReactInstance::ReactInstance(
|
||||
std::unique_ptr<jsi::Runtime> runtime,
|
||||
std::shared_ptr<MessageQueueThread> jsMessageQueueThread,
|
||||
std::shared_ptr<TimerManager> timerManager,
|
||||
JsErrorHandler::JsErrorHandlingFunc jsErrorHandlingFunc)
|
||||
: runtime_(std::move(runtime)),
|
||||
jsMessageQueueThread_(jsMessageQueueThread),
|
||||
timerManager_(std::move(timerManager)),
|
||||
jsErrorHandler_(jsErrorHandlingFunc),
|
||||
hasFatalJsError_(std::make_shared<bool>(false)) {
|
||||
auto runtimeExecutor = [weakRuntime = std::weak_ptr<jsi::Runtime>(runtime_),
|
||||
weakTimerManager =
|
||||
std::weak_ptr<TimerManager>(timerManager_),
|
||||
weakJsMessageQueueThread =
|
||||
std::weak_ptr<MessageQueueThread>(
|
||||
jsMessageQueueThread_),
|
||||
weakHasFatalJsError =
|
||||
std::weak_ptr<bool>(hasFatalJsError_)](
|
||||
std::function<void(jsi::Runtime & runtime)>
|
||||
&&callback) {
|
||||
if (std::shared_ptr<bool> sharedHasFatalJsError =
|
||||
weakHasFatalJsError.lock()) {
|
||||
if (*sharedHasFatalJsError) {
|
||||
LOG(INFO)
|
||||
<< "Calling into JS using runtimeExecutor but hasFatalJsError_ is true";
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (weakRuntime.expired()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::shared_ptr<MessageQueueThread> sharedJsMessageQueueThread =
|
||||
weakJsMessageQueueThread.lock()) {
|
||||
sharedJsMessageQueueThread->runOnQueue(
|
||||
[weakRuntime, weakTimerManager, callback = std::move(callback)]() {
|
||||
if (auto strongRuntime = weakRuntime.lock()) {
|
||||
SystraceSection s("ReactInstance::_runtimeExecutor[Callback]");
|
||||
try {
|
||||
callback(*strongRuntime);
|
||||
if (auto strongTimerManager = weakTimerManager.lock()) {
|
||||
strongTimerManager->callReactNativeMicrotasks(*strongRuntime);
|
||||
}
|
||||
performMicrotaskCheckpoint(*strongRuntime);
|
||||
} catch (jsi::JSError &originalError) {
|
||||
handleJSError(*strongRuntime, originalError, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
runtimeScheduler_ =
|
||||
std::make_shared<RuntimeScheduler>(std::move(runtimeExecutor));
|
||||
|
||||
auto pipedRuntimeExecutor =
|
||||
[runtimeScheduler = runtimeScheduler_.get()](
|
||||
std::function<void(jsi::Runtime & runtime)> &&callback) {
|
||||
runtimeScheduler->scheduleWork(std::move(callback));
|
||||
};
|
||||
|
||||
bufferedRuntimeExecutor_ =
|
||||
std::make_shared<BufferedRuntimeExecutor>(pipedRuntimeExecutor);
|
||||
}
|
||||
|
||||
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
|
||||
// getBufferedRuntimeExecutor() 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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::shared_ptr<RuntimeScheduler>
|
||||
ReactInstance::getRuntimeScheduler() noexcept {
|
||||
return runtimeScheduler_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a property on the global object that is neither enumerable, nor
|
||||
* configurable, nor writable. This ensures that the private globals exposed by
|
||||
* ReactInstance cannot overwritten by third-party JavaScript code. It also
|
||||
* ensures that third-party JavaScript code unaware of these globals isn't able
|
||||
* to accidentally access them. In JavaScript, equivalent to:
|
||||
*
|
||||
* Object.defineProperty(global, propName, {
|
||||
* value: value
|
||||
* })
|
||||
*/
|
||||
static void defineReadOnlyGlobal(
|
||||
jsi::Runtime &runtime,
|
||||
std::string propName,
|
||||
jsi::Value &&value) {
|
||||
jsi::Object jsObject =
|
||||
runtime.global().getProperty(runtime, "Object").asObject(runtime);
|
||||
jsi::Function defineProperty = jsObject.getProperty(runtime, "defineProperty")
|
||||
.asObject(runtime)
|
||||
.asFunction(runtime);
|
||||
|
||||
jsi::Object descriptor = jsi::Object(runtime);
|
||||
descriptor.setProperty(runtime, "value", std::move(value));
|
||||
defineProperty.callWithThis(
|
||||
runtime,
|
||||
jsObject,
|
||||
runtime.global(),
|
||||
jsi::String::createFromUtf8(runtime, propName),
|
||||
descriptor);
|
||||
}
|
||||
|
||||
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) {
|
||||
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_)](jsi::Runtime &runtime) {
|
||||
try {
|
||||
SystraceSection s("ReactInstance::loadScript");
|
||||
bool hasLogger(ReactMarker::logTaggedMarkerBridgelessImpl);
|
||||
if (hasLogger) {
|
||||
ReactMarker::logTaggedMarkerBridgeless(
|
||||
ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
|
||||
}
|
||||
|
||||
runtime.evaluateJavaScript(buffer, sourceURL);
|
||||
if (hasLogger) {
|
||||
ReactMarker::logTaggedMarkerBridgeless(
|
||||
ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
|
||||
}
|
||||
if (auto strongBufferedRuntimeExecuter =
|
||||
weakBufferedRuntimeExecuter.lock()) {
|
||||
strongBufferedRuntimeExecuter->flush();
|
||||
}
|
||||
} catch (jsi::JSError &error) {
|
||||
// Handle uncaught JS errors during loading JS bundle
|
||||
*hasFatalJsError_ = true;
|
||||
this->jsErrorHandler_.handleJsError(error, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* 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,
|
||||
const folly::dynamic &args) {
|
||||
bufferedRuntimeExecutor_->execute([=](jsi::Runtime &runtime) {
|
||||
SystraceSection s(
|
||||
"ReactInstance::callFunctionOnModule",
|
||||
"moduleName",
|
||||
moduleName,
|
||||
"methodName",
|
||||
methodName);
|
||||
if (modules_.find(moduleName) == modules_.end()) {
|
||||
std::ostringstream knownModules;
|
||||
int i = 0;
|
||||
for (auto it = modules_.begin(); it != modules_.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(modules_.size()) + "):" + knownModules.str() +
|
||||
". Did you forget to call `RN$registerCallableModule`?");
|
||||
}
|
||||
|
||||
auto module = modules_[moduleName]->factory.call(runtime).asObject(runtime);
|
||||
auto method = module.getProperty(runtime, methodName.c_str());
|
||||
if (method.isUndefined()) {
|
||||
throw jsi::JSError(
|
||||
runtime,
|
||||
"Failed to call into JavaScript module method " + moduleName + "." +
|
||||
methodName + ". Module exists, but the method is undefined.");
|
||||
}
|
||||
|
||||
std::vector<jsi::Value> jsArgs;
|
||||
for (auto &arg : args) {
|
||||
jsArgs.push_back(jsi::valueFromDynamic(runtime, arg));
|
||||
}
|
||||
method.asObject(runtime).asFunction(runtime).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));
|
||||
|
||||
jsi::addNativeTracingHooks(runtime);
|
||||
|
||||
if (options.isProfiling) {
|
||||
defineReadOnlyGlobal(runtime, "__RCTProfileIsProfiling", jsi::Value(true));
|
||||
}
|
||||
|
||||
if (options.runtimeDiagnosticFlags.length() > 0) {
|
||||
defineReadOnlyGlobal(
|
||||
runtime,
|
||||
"RN$DiagnosticFlags",
|
||||
jsi::String::createFromUtf8(runtime, options.runtimeDiagnosticFlags));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ReactInstance::initializeRuntime(
|
||||
JSRuntimeFlags options,
|
||||
BindingsInstallFunc bindingsInstallFunc) noexcept {
|
||||
runtimeScheduler_->scheduleWork([this, options, bindingsInstallFunc](
|
||||
jsi::Runtime &runtime) {
|
||||
SystraceSection s("ReactInstance::initializeRuntime");
|
||||
RuntimeSchedulerBinding::createAndInstallIfNeeded(
|
||||
runtime, runtimeScheduler_);
|
||||
|
||||
defineReactInstanceFlags(runtime, options);
|
||||
|
||||
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].asObject(runtime).isFunction(runtime)) {
|
||||
throw jsi::JSError(
|
||||
runtime,
|
||||
"The second argument to registerCallableModule must be a function that returns the JS module.");
|
||||
}
|
||||
modules_[name] = std::make_shared<CallableModule>(
|
||||
args[1].getObject(runtime).asFunction(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;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,75 @@
|
|||
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <JsErrorHandler/JsErrorHandler.h>
|
||||
#include <ReactCommon/RuntimeExecutor.h>
|
||||
#include <cxxreact/MessageQueueThread.h>
|
||||
#include <jsi/jsi.h>
|
||||
#include <jsireact/JSIExecutor.h>
|
||||
#include <react/bridgeless/BufferedRuntimeExecutor.h>
|
||||
#include <react/bridgeless/TimerManager.h>
|
||||
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
struct CallableModule {
|
||||
explicit CallableModule(jsi::Function factory)
|
||||
: factory(std::move(factory)) {}
|
||||
jsi::Function factory;
|
||||
};
|
||||
|
||||
class ReactInstance final {
|
||||
public:
|
||||
using BindingsInstallFunc = std::function<void(jsi::Runtime &runtime)>;
|
||||
|
||||
ReactInstance(
|
||||
std::unique_ptr<jsi::Runtime> runtime,
|
||||
std::shared_ptr<MessageQueueThread> jsMessageQueueThread,
|
||||
std::shared_ptr<TimerManager> timerManager,
|
||||
JsErrorHandler::JsErrorHandlingFunc JsErrorHandlingFunc);
|
||||
|
||||
RuntimeExecutor getUnbufferedRuntimeExecutor() noexcept;
|
||||
|
||||
RuntimeExecutor getBufferedRuntimeExecutor() noexcept;
|
||||
|
||||
std::shared_ptr<RuntimeScheduler> getRuntimeScheduler() noexcept;
|
||||
|
||||
struct JSRuntimeFlags {
|
||||
bool isProfiling = false;
|
||||
const std::string runtimeDiagnosticFlags = "";
|
||||
};
|
||||
|
||||
void initializeRuntime(
|
||||
JSRuntimeFlags options,
|
||||
BindingsInstallFunc bindingsInstallFunc) noexcept;
|
||||
|
||||
void loadScript(
|
||||
std::unique_ptr<const JSBigString> script,
|
||||
const std::string &sourceURL);
|
||||
|
||||
void registerSegment(uint32_t segmentId, const std::string &segmentPath);
|
||||
|
||||
void callFunctionOnModule(
|
||||
const std::string &moduleName,
|
||||
const std::string &methodName,
|
||||
const folly::dynamic &args);
|
||||
|
||||
void handleMemoryPressureJs(int pressureLevel);
|
||||
|
||||
private:
|
||||
std::shared_ptr<jsi::Runtime> runtime_;
|
||||
std::shared_ptr<MessageQueueThread> jsMessageQueueThread_;
|
||||
std::shared_ptr<BufferedRuntimeExecutor> bufferedRuntimeExecutor_;
|
||||
std::shared_ptr<TimerManager> timerManager_;
|
||||
std::unordered_map<std::string, std::shared_ptr<CallableModule>> modules_;
|
||||
std::shared_ptr<RuntimeScheduler> runtimeScheduler_;
|
||||
JsErrorHandler jsErrorHandler_;
|
||||
|
||||
// Whether there are errors caught during bundle loading
|
||||
std::shared_ptr<bool> hasFatalJsError_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,411 @@
|
|||
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
|
||||
#include "TimerManager.h"
|
||||
|
||||
#include <cxxreact/SystraceSection.h>
|
||||
#include <utility>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
TimerManager::TimerManager(
|
||||
std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry) noexcept
|
||||
: platformTimerRegistry_(std::move(platformTimerRegistry)) {}
|
||||
|
||||
void TimerManager::setRuntimeExecutor(
|
||||
RuntimeExecutor runtimeExecutor) noexcept {
|
||||
runtimeExecutor_ = runtimeExecutor;
|
||||
}
|
||||
|
||||
std::shared_ptr<TimerHandle> TimerManager::createReactNativeMicrotask(
|
||||
jsi::Function &&callback,
|
||||
std::vector<jsi::Value> &&args) {
|
||||
auto sharedCallback = std::make_shared<TimerCallback>(
|
||||
std::move(callback), std::move(args), /* repeat */ false);
|
||||
|
||||
// Get the id for the callback.
|
||||
uint32_t timerID = timerIndex_++;
|
||||
timers_[timerID] = std::move(sharedCallback);
|
||||
|
||||
reactNativeMicrotasksQueue_.push_back(timerID);
|
||||
return std::make_shared<TimerHandle>(timerID);
|
||||
}
|
||||
|
||||
void TimerManager::callReactNativeMicrotasks(jsi::Runtime &runtime) {
|
||||
std::vector<uint32_t> reactNativeMicrotasksQueue;
|
||||
while (!reactNativeMicrotasksQueue_.empty()) {
|
||||
reactNativeMicrotasksQueue.clear();
|
||||
reactNativeMicrotasksQueue.swap(reactNativeMicrotasksQueue_);
|
||||
|
||||
for (auto reactNativeMicrotaskID : reactNativeMicrotasksQueue) {
|
||||
// ReactNativeMicrotasks can clear other scheduled reactNativeMicrotasks.
|
||||
if (timers_.count(reactNativeMicrotaskID) > 0) {
|
||||
timers_[reactNativeMicrotaskID]->invoke(runtime);
|
||||
timers_.erase(reactNativeMicrotaskID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<TimerHandle> TimerManager::createTimer(
|
||||
jsi::Function &&callback,
|
||||
std::vector<jsi::Value> &&args,
|
||||
double delay) {
|
||||
auto sharedCallback = std::make_shared<TimerCallback>(
|
||||
std::move(callback), std::move(args), false);
|
||||
|
||||
// Get the id for the callback.
|
||||
uint32_t timerID = timerIndex_++;
|
||||
timers_[timerID] = std::move(sharedCallback);
|
||||
|
||||
platformTimerRegistry_->createTimer(timerID, delay);
|
||||
|
||||
return std::make_shared<TimerHandle>(timerID);
|
||||
}
|
||||
|
||||
std::shared_ptr<TimerHandle> TimerManager::createRecurringTimer(
|
||||
jsi::Function &&callback,
|
||||
std::vector<jsi::Value> &&args,
|
||||
double delay) {
|
||||
auto sharedCallback = std::make_shared<TimerCallback>(
|
||||
std::move(callback), std::move(args), true);
|
||||
|
||||
// Get the id for the callback.
|
||||
uint32_t timerID = timerIndex_++;
|
||||
timers_[timerID] = std::move(sharedCallback);
|
||||
|
||||
platformTimerRegistry_->createRecurringTimer(timerID, delay);
|
||||
|
||||
return std::make_shared<TimerHandle>(timerID);
|
||||
}
|
||||
|
||||
void TimerManager::deleteReactNativeMicrotask(
|
||||
jsi::Runtime &runtime,
|
||||
std::shared_ptr<TimerHandle> timerHandle) {
|
||||
if (timerHandle == nullptr) {
|
||||
throw jsi::JSError(
|
||||
runtime, "clearReactNativeMicrotask was called with an invalid handle");
|
||||
}
|
||||
|
||||
for (auto it = reactNativeMicrotasksQueue_.begin();
|
||||
it != reactNativeMicrotasksQueue_.end();
|
||||
it++) {
|
||||
if ((*it) == timerHandle->index()) {
|
||||
reactNativeMicrotasksQueue_.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (timers_.find(timerHandle->index()) != timers_.end()) {
|
||||
timers_.erase(timerHandle->index());
|
||||
}
|
||||
}
|
||||
|
||||
void TimerManager::deleteTimer(
|
||||
jsi::Runtime &runtime,
|
||||
std::shared_ptr<TimerHandle> timerHandle) {
|
||||
if (timerHandle == nullptr) {
|
||||
throw jsi::JSError(runtime, "clearTimeout called with an invalid handle");
|
||||
}
|
||||
|
||||
platformTimerRegistry_->deleteTimer(timerHandle->index());
|
||||
if (timers_.find(timerHandle->index()) != timers_.end()) {
|
||||
timers_.erase(timerHandle->index());
|
||||
}
|
||||
}
|
||||
|
||||
void TimerManager::deleteRecurringTimer(
|
||||
jsi::Runtime &runtime,
|
||||
std::shared_ptr<TimerHandle> timerHandle) {
|
||||
if (timerHandle == nullptr) {
|
||||
throw jsi::JSError(runtime, "clearInterval called with an invalid handle");
|
||||
}
|
||||
|
||||
platformTimerRegistry_->deleteTimer(timerHandle->index());
|
||||
if (timers_.find(timerHandle->index()) != timers_.end()) {
|
||||
timers_.erase(timerHandle->index());
|
||||
}
|
||||
}
|
||||
|
||||
void TimerManager::callTimer(uint32_t timerID) {
|
||||
runtimeExecutor_([this, timerID](jsi::Runtime &runtime) {
|
||||
SystraceSection s("TimerManager::callTimer");
|
||||
if (timers_.count(timerID) > 0) {
|
||||
timers_[timerID]->invoke(runtime);
|
||||
// Invoking a timer has the potential to delete it. Double check the timer
|
||||
// still exists before accessing it again.
|
||||
if (timers_.count(timerID) > 0 && !timers_[timerID]->repeat) {
|
||||
timers_.erase(timerID);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void TimerManager::attachGlobals(jsi::Runtime &runtime) {
|
||||
// Install host functions for timers.
|
||||
// TODO (T45786383): Add missing timer functions from JSTimers
|
||||
// TODL (T96212789): Skip immediate APIs when JSVM microtask queue is used.
|
||||
runtime.global().setProperty(
|
||||
runtime,
|
||||
"setImmediate",
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "setImmediate"),
|
||||
2, // Function, ...args
|
||||
[this](
|
||||
jsi::Runtime &rt,
|
||||
const jsi::Value &thisVal,
|
||||
const jsi::Value *args,
|
||||
size_t count) {
|
||||
if (count == 0) {
|
||||
throw jsi::JSError(
|
||||
rt,
|
||||
"setImmediate must be called with at least one argument (a function to call)");
|
||||
}
|
||||
|
||||
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
|
||||
throw jsi::JSError(
|
||||
rt, "The first argument to setImmediate must be a function.");
|
||||
}
|
||||
auto callback = args[0].getObject(rt).getFunction(rt);
|
||||
|
||||
// Package up the remaining argument values into one place.
|
||||
std::vector<jsi::Value> moreArgs;
|
||||
for (size_t extraArgNum = 1; extraArgNum < count; extraArgNum++) {
|
||||
moreArgs.emplace_back(rt, args[extraArgNum]);
|
||||
}
|
||||
|
||||
auto handle = createReactNativeMicrotask(
|
||||
std::move(callback), std::move(moreArgs));
|
||||
return jsi::Object::createFromHostObject(rt, handle);
|
||||
}));
|
||||
|
||||
runtime.global().setProperty(
|
||||
runtime,
|
||||
"clearImmediate",
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "clearImmediate"),
|
||||
1, // handle
|
||||
[this](
|
||||
jsi::Runtime &rt,
|
||||
const jsi::Value &thisVal,
|
||||
const jsi::Value *args,
|
||||
size_t count) {
|
||||
if (count == 0 || !args[0].isObject() ||
|
||||
!args[0].asObject(rt).isHostObject<TimerHandle>(rt)) {
|
||||
return jsi::Value::undefined();
|
||||
}
|
||||
std::shared_ptr<TimerHandle> handle =
|
||||
args[0].asObject(rt).asHostObject<TimerHandle>(rt);
|
||||
deleteReactNativeMicrotask(rt, handle);
|
||||
return jsi::Value::undefined();
|
||||
}));
|
||||
|
||||
runtime.global().setProperty(
|
||||
runtime,
|
||||
"setTimeout",
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "setTimeout"),
|
||||
3, // Function, delay, ...args
|
||||
[this](
|
||||
jsi::Runtime &rt,
|
||||
const jsi::Value &thisVal,
|
||||
const jsi::Value *args,
|
||||
size_t count) {
|
||||
if (count == 0) {
|
||||
throw jsi::JSError(
|
||||
rt,
|
||||
"setTimeout must be called with at least one argument (the function to call).");
|
||||
}
|
||||
|
||||
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
|
||||
throw jsi::JSError(
|
||||
rt, "The first argument to setTimeout must be a function.");
|
||||
}
|
||||
auto callback = args[0].getObject(rt).getFunction(rt);
|
||||
|
||||
if (count > 1 && !args[1].isNumber() && !args[1].isUndefined()) {
|
||||
throw jsi::JSError(
|
||||
rt,
|
||||
"The second argument to setTimeout must be a number or undefined.");
|
||||
}
|
||||
auto delay =
|
||||
count > 1 && args[1].isNumber() ? args[1].getNumber() : 0;
|
||||
|
||||
// Package up the remaining argument values into one place.
|
||||
std::vector<jsi::Value> moreArgs;
|
||||
for (size_t extraArgNum = 2; extraArgNum < count; extraArgNum++) {
|
||||
moreArgs.emplace_back(rt, args[extraArgNum]);
|
||||
}
|
||||
|
||||
auto handle =
|
||||
createTimer(std::move(callback), std::move(moreArgs), delay);
|
||||
return jsi::Object::createFromHostObject(rt, handle);
|
||||
}));
|
||||
|
||||
runtime.global().setProperty(
|
||||
runtime,
|
||||
"clearTimeout",
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "clearTimeout"),
|
||||
1, // timerID
|
||||
[this](
|
||||
jsi::Runtime &rt,
|
||||
const jsi::Value &thisVal,
|
||||
const jsi::Value *args,
|
||||
size_t count) {
|
||||
if (count == 0 || !args[0].isObject() ||
|
||||
!args[0].asObject(rt).isHostObject<TimerHandle>(rt)) {
|
||||
return jsi::Value::undefined();
|
||||
}
|
||||
std::shared_ptr<TimerHandle> host =
|
||||
args[0].asObject(rt).asHostObject<TimerHandle>(rt);
|
||||
deleteTimer(rt, host);
|
||||
return jsi::Value::undefined();
|
||||
}));
|
||||
|
||||
runtime.global().setProperty(
|
||||
runtime,
|
||||
"setInterval",
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "setInterval"),
|
||||
3, // Function, delay, ...args
|
||||
[this](
|
||||
jsi::Runtime &rt,
|
||||
const jsi::Value &thisVal,
|
||||
const jsi::Value *args,
|
||||
size_t count) {
|
||||
if (count < 2) {
|
||||
throw jsi::JSError(
|
||||
rt,
|
||||
"setInterval must be called with at least two arguments (the function to call and the delay).");
|
||||
}
|
||||
|
||||
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
|
||||
throw jsi::JSError(
|
||||
rt, "The first argument to setInterval must be a function.");
|
||||
}
|
||||
auto callback = args[0].getObject(rt).getFunction(rt);
|
||||
|
||||
if (!args[1].isNumber()) {
|
||||
throw jsi::JSError(
|
||||
rt, "The second argument to setInterval must be a number.");
|
||||
}
|
||||
auto delay = args[1].getNumber();
|
||||
|
||||
// Package up the remaining argument values into one place.
|
||||
std::vector<jsi::Value> moreArgs;
|
||||
for (size_t extraArgNum = 2; extraArgNum < count; extraArgNum++) {
|
||||
moreArgs.emplace_back(rt, args[extraArgNum]);
|
||||
}
|
||||
|
||||
auto handle = createRecurringTimer(
|
||||
std::move(callback), std::move(moreArgs), delay);
|
||||
return jsi::Object::createFromHostObject(rt, handle);
|
||||
}));
|
||||
|
||||
runtime.global().setProperty(
|
||||
runtime,
|
||||
"clearInterval",
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "clearInterval"),
|
||||
1, // timerID
|
||||
[this](
|
||||
jsi::Runtime &rt,
|
||||
const jsi::Value &thisVal,
|
||||
const jsi::Value *args,
|
||||
size_t count) {
|
||||
if (count == 0 || !args[0].isObject() ||
|
||||
!args[0].asObject(rt).isHostObject<TimerHandle>(rt)) {
|
||||
return jsi::Value::undefined();
|
||||
}
|
||||
std::shared_ptr<TimerHandle> host =
|
||||
args[0].asObject(rt).asHostObject<TimerHandle>(rt);
|
||||
deleteRecurringTimer(rt, host);
|
||||
return jsi::Value::undefined();
|
||||
}));
|
||||
|
||||
runtime.global().setProperty(
|
||||
runtime,
|
||||
"requestAnimationFrame",
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "requestAnimationFrame"),
|
||||
1, // callback
|
||||
[this](
|
||||
jsi::Runtime &rt,
|
||||
const jsi::Value &thisVal,
|
||||
const jsi::Value *args,
|
||||
size_t count) {
|
||||
if (count == 0) {
|
||||
throw jsi::JSError(
|
||||
rt,
|
||||
"requestAnimationFrame must be called with at least one argument (i.e: a callback)");
|
||||
}
|
||||
|
||||
if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
|
||||
throw jsi::JSError(
|
||||
rt,
|
||||
"The first argument to requestAnimationFrame must be a function.");
|
||||
}
|
||||
|
||||
using CallbackContainer = std::tuple<jsi::Function>;
|
||||
auto callback = jsi::Function::createFromHostFunction(
|
||||
rt,
|
||||
jsi::PropNameID::forAscii(rt, "RN$rafFn"),
|
||||
0,
|
||||
[callbackContainer = std::make_shared<CallbackContainer>(
|
||||
args[0].getObject(rt).getFunction(rt))](
|
||||
jsi::Runtime &rt,
|
||||
const jsi::Value &thisVal,
|
||||
const jsi::Value *args,
|
||||
size_t count) {
|
||||
auto performance =
|
||||
rt.global().getPropertyAsObject(rt, "performance");
|
||||
auto nowFn = performance.getPropertyAsFunction(rt, "now");
|
||||
auto now = nowFn.callWithThis(rt, performance, {});
|
||||
|
||||
return std::get<0>(*callbackContainer)
|
||||
.call(rt, {std::move(now)});
|
||||
});
|
||||
|
||||
// The current implementation of requestAnimationFrame is the same
|
||||
// as setTimeout(0). This isn't exactly how requestAnimationFrame
|
||||
// is supposed to work on web, and may change in the future.
|
||||
auto handle = createTimer(
|
||||
std::move(callback),
|
||||
std::vector<jsi::Value>(),
|
||||
/* delay */ 0);
|
||||
return jsi::Object::createFromHostObject(rt, handle);
|
||||
}));
|
||||
|
||||
runtime.global().setProperty(
|
||||
runtime,
|
||||
"cancelAnimationFrame",
|
||||
jsi::Function::createFromHostFunction(
|
||||
runtime,
|
||||
jsi::PropNameID::forAscii(runtime, "cancelAnimationFrame"),
|
||||
1, // timerID
|
||||
[this](
|
||||
jsi::Runtime &rt,
|
||||
const jsi::Value &thisVal,
|
||||
const jsi::Value *args,
|
||||
size_t count) {
|
||||
if (count == 0 || !args[0].isObject() ||
|
||||
!args[0].asObject(rt).isHostObject<TimerHandle>(rt)) {
|
||||
return jsi::Value::undefined();
|
||||
}
|
||||
std::shared_ptr<TimerHandle> host =
|
||||
args[0].asObject(rt).asHostObject<TimerHandle>(rt);
|
||||
deleteTimer(rt, host);
|
||||
return jsi::Value::undefined();
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,112 @@
|
|||
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ReactCommon/RuntimeExecutor.h>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "PlatformTimerRegistry.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
/*
|
||||
* A HostObject subclass representing the result of a setTimeout call.
|
||||
* Can be used as an argument to clearTimeout.
|
||||
*/
|
||||
class TimerHandle : public jsi::HostObject {
|
||||
public:
|
||||
explicit TimerHandle(uint32_t index) : index_(index) {}
|
||||
|
||||
uint32_t index() const {
|
||||
return index_;
|
||||
}
|
||||
|
||||
~TimerHandle() override = default;
|
||||
|
||||
private:
|
||||
// Index in the timeouts_ map of the owning SetTimeoutQueue.
|
||||
uint32_t index_;
|
||||
};
|
||||
|
||||
/*
|
||||
* Wraps a jsi::Function to make it copyable so we can pass it into a lambda.
|
||||
*/
|
||||
struct TimerCallback {
|
||||
TimerCallback(TimerCallback &&) = default;
|
||||
|
||||
TimerCallback(
|
||||
jsi::Function callback,
|
||||
std::vector<jsi::Value> args,
|
||||
bool repeat)
|
||||
: callback_(std::move(callback)),
|
||||
args_(std::move(args)),
|
||||
repeat(repeat) {}
|
||||
|
||||
void invoke(jsi::Runtime &runtime) {
|
||||
callback_.call(runtime, args_.data(), args_.size());
|
||||
}
|
||||
|
||||
jsi::Function callback_;
|
||||
const std::vector<jsi::Value> args_;
|
||||
bool repeat;
|
||||
};
|
||||
|
||||
class TimerManager {
|
||||
public:
|
||||
explicit TimerManager(
|
||||
std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry) noexcept;
|
||||
|
||||
void setRuntimeExecutor(RuntimeExecutor runtimeExecutor) noexcept;
|
||||
|
||||
void callReactNativeMicrotasks(jsi::Runtime &runtime);
|
||||
|
||||
void callTimer(uint32_t);
|
||||
|
||||
void attachGlobals(jsi::Runtime &runtime);
|
||||
|
||||
private:
|
||||
std::shared_ptr<TimerHandle> createReactNativeMicrotask(
|
||||
jsi::Function &&callback,
|
||||
std::vector<jsi::Value> &&args);
|
||||
|
||||
void deleteReactNativeMicrotask(
|
||||
jsi::Runtime &runtime,
|
||||
std::shared_ptr<TimerHandle> handle);
|
||||
|
||||
std::shared_ptr<TimerHandle> createTimer(
|
||||
jsi::Function &&callback,
|
||||
std::vector<jsi::Value> &&args,
|
||||
double delay);
|
||||
|
||||
void deleteTimer(jsi::Runtime &runtime, std::shared_ptr<TimerHandle> handle);
|
||||
|
||||
std::shared_ptr<TimerHandle> createRecurringTimer(
|
||||
jsi::Function &&callback,
|
||||
std::vector<jsi::Value> &&args,
|
||||
double delay);
|
||||
|
||||
void deleteRecurringTimer(
|
||||
jsi::Runtime &runtime,
|
||||
std::shared_ptr<TimerHandle> handle);
|
||||
|
||||
RuntimeExecutor runtimeExecutor_;
|
||||
std::unique_ptr<PlatformTimerRegistry> platformTimerRegistry_;
|
||||
|
||||
// A map (id => callback func) of the currently active JS timers
|
||||
std::unordered_map<uint32_t, std::shared_ptr<TimerCallback>> timers_;
|
||||
|
||||
// Each timeout that is registered on this queue gets a sequential id. This
|
||||
// is the global count from which those are assigned.
|
||||
uint64_t timerIndex_{0};
|
||||
|
||||
// The React Native microtask queue is used to back public APIs including
|
||||
// `queueMicrotask`, `clearImmediate`, and `setImmediate` (which is used by
|
||||
// the Promise polyfill) when the JSVM microtask mechanism is not used.
|
||||
std::vector<uint32_t> reactNativeMicrotasksQueue_;
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
|
@ -0,0 +1,865 @@
|
|||
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
||||
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <ReactCommon/RuntimeExecutor.h>
|
||||
#include <ReactNative/venice/ReactInstance.h>
|
||||
#include <hermes/API/hermes/hermes.h>
|
||||
#include <jsi/jsi.h>
|
||||
#include <react/renderer/mapbuffer/MapBuffer.h>
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::SaveArg;
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class MockTimerRegistry : public PlatformTimerRegistry {
|
||||
public:
|
||||
MOCK_METHOD2(createTimer, void(uint32_t, double));
|
||||
MOCK_METHOD2(createRecurringTimer, void(uint32_t, double));
|
||||
MOCK_METHOD1(deleteTimer, void(uint32_t));
|
||||
};
|
||||
|
||||
class MockMessageQueueThread : public MessageQueueThread {
|
||||
public:
|
||||
void runOnQueue(std::function<void()> &&func) {
|
||||
callbackQueue_.push(func);
|
||||
}
|
||||
|
||||
// Unused
|
||||
void runOnQueueSync(std::function<void()> &&) {}
|
||||
|
||||
// Unused
|
||||
void quitSynchronous() {}
|
||||
|
||||
void tick() {
|
||||
if (!callbackQueue_.empty()) {
|
||||
auto callback = callbackQueue_.front();
|
||||
callback();
|
||||
callbackQueue_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void guardedTick() {
|
||||
try {
|
||||
tick();
|
||||
} catch (const std::exception &e) {
|
||||
// For easier debugging
|
||||
FAIL() << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() {
|
||||
return callbackQueue_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<std::function<void()>> callbackQueue_;
|
||||
};
|
||||
|
||||
class ErrorUtils : public jsi::HostObject {
|
||||
public:
|
||||
jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &name) override {
|
||||
auto methodName = name.utf8(rt);
|
||||
|
||||
if (methodName == "reportFatalError") {
|
||||
return jsi::Function::createFromHostFunction(
|
||||
rt,
|
||||
name,
|
||||
1,
|
||||
[this](
|
||||
jsi::Runtime &runtime,
|
||||
const jsi::Value &thisValue,
|
||||
const jsi::Value *arguments,
|
||||
size_t count) {
|
||||
if (count >= 1) {
|
||||
auto value = jsi::Value(runtime, std::move(arguments[0]));
|
||||
auto error = jsi::JSError(runtime, std::move(value));
|
||||
reportFatalError(std::move(error));
|
||||
}
|
||||
return jsi::Value::undefined();
|
||||
});
|
||||
} else {
|
||||
throw std::runtime_error("Unknown method: " + methodName);
|
||||
}
|
||||
}
|
||||
|
||||
void reportFatalError(jsi::JSError &&error) {
|
||||
errors_.push_back(std::move(error));
|
||||
}
|
||||
|
||||
size_t size() {
|
||||
return errors_.size();
|
||||
}
|
||||
|
||||
jsi::JSError getLastError() {
|
||||
auto error = errors_.back();
|
||||
errors_.pop_back();
|
||||
return error;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<jsi::JSError> errors_;
|
||||
};
|
||||
|
||||
class ReactInstanceTest : public ::testing::Test {
|
||||
protected:
|
||||
ReactInstanceTest() {}
|
||||
|
||||
void SetUp() override {
|
||||
auto runtime = hermes::makeHermesRuntime();
|
||||
runtime_ = runtime.get();
|
||||
messageQueueThread_ = std::make_shared<MockMessageQueueThread>();
|
||||
auto mockRegistry = std::make_unique<MockTimerRegistry>();
|
||||
mockRegistry_ = mockRegistry.get();
|
||||
timerManager_ = std::make_shared<TimerManager>(std::move(mockRegistry));
|
||||
auto jsErrorHandlingFunc = [](MapBuffer errorMap) noexcept {
|
||||
// Do nothing
|
||||
};
|
||||
instance_ = std::make_unique<ReactInstance>(
|
||||
std::move(runtime),
|
||||
messageQueueThread_,
|
||||
timerManager_,
|
||||
std::move(jsErrorHandlingFunc));
|
||||
timerManager_->setRuntimeExecutor(instance_->getBufferedRuntimeExecutor());
|
||||
|
||||
// Install a C++ error handler
|
||||
errorHandler_ = std::make_shared<ErrorUtils>();
|
||||
runtime_->global().setProperty(
|
||||
*runtime_,
|
||||
"ErrorUtils",
|
||||
jsi::Object::createFromHostObject(*runtime_, errorHandler_));
|
||||
}
|
||||
|
||||
void initializeRuntimeWithScript(
|
||||
ReactInstance::JSRuntimeFlags jsRuntimeFlags,
|
||||
std::string script) {
|
||||
instance_->initializeRuntime(jsRuntimeFlags, [](jsi::Runtime &runtime) {});
|
||||
step();
|
||||
|
||||
// Run the main bundle, so that native -> JS calls no longer get buffered.
|
||||
loadScript(script);
|
||||
}
|
||||
|
||||
void initializeRuntimeWithScript(std::string script) {
|
||||
instance_->initializeRuntime(
|
||||
{.isProfiling = false}, [](jsi::Runtime &runtime) {});
|
||||
step();
|
||||
|
||||
// Run the main bundle, so that native -> JS calls no longer get buffered.
|
||||
loadScript(script);
|
||||
}
|
||||
|
||||
jsi::Value eval(std::string js) {
|
||||
RuntimeExecutor runtimeExecutor = instance_->getUnbufferedRuntimeExecutor();
|
||||
jsi::Value ret = jsi::Value::undefined();
|
||||
runtimeExecutor([js, &ret](jsi::Runtime &runtime) {
|
||||
ret = runtime.evaluateJavaScript(
|
||||
std::make_unique<jsi::StringBuffer>(js), "");
|
||||
});
|
||||
step();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Call instance_->loadScript() to evaluate JS script and flush buffered JS
|
||||
// calls
|
||||
jsi::Value loadScript(std::string js) {
|
||||
jsi::Value ret = jsi::Value::undefined();
|
||||
instance_->loadScript(std::make_unique<JSBigStdString>(std::move(js)), "");
|
||||
step();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void expectError() {
|
||||
EXPECT_NE(errorHandler_->size(), 0)
|
||||
<< "Expected an error to have been thrown, but it wasn't.";
|
||||
}
|
||||
|
||||
void expectNoError() {
|
||||
EXPECT_EQ(errorHandler_->size(), 0)
|
||||
<< "Expected no error to have been thrown, but one was.";
|
||||
}
|
||||
|
||||
std::string getLastErrorMessage() {
|
||||
auto error = errorHandler_->getLastError();
|
||||
return error.getMessage();
|
||||
}
|
||||
|
||||
std::string getErrorMessage(std::string js) {
|
||||
eval(js);
|
||||
return getLastErrorMessage();
|
||||
}
|
||||
|
||||
void step() {
|
||||
messageQueueThread_->guardedTick();
|
||||
}
|
||||
|
||||
jsi::Runtime *runtime_;
|
||||
std::shared_ptr<MockMessageQueueThread> messageQueueThread_;
|
||||
std::unique_ptr<ReactInstance> instance_;
|
||||
std::shared_ptr<TimerManager> timerManager_;
|
||||
MockTimerRegistry *mockRegistry_;
|
||||
std::shared_ptr<ErrorUtils> errorHandler_;
|
||||
};
|
||||
|
||||
TEST_F(ReactInstanceTest, testBridgelessFlagIsSet) {
|
||||
eval("RN$Bridgeless === true");
|
||||
expectError();
|
||||
initializeRuntimeWithScript("");
|
||||
auto val = eval("RN$Bridgeless === true");
|
||||
EXPECT_EQ(val.getBool(), true);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testProfilingFlag) {
|
||||
eval("__RCTProfileIsProfiling === true");
|
||||
expectError();
|
||||
initializeRuntimeWithScript({.isProfiling = true}, "");
|
||||
auto val = eval("__RCTProfileIsProfiling === true");
|
||||
EXPECT_EQ(val.getBool(), true);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testPromiseIntegration) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
eval(R"xyz123(
|
||||
let called = 0;
|
||||
function getResult() {
|
||||
return called;
|
||||
}
|
||||
Promise.resolve().then(() => {
|
||||
called++;
|
||||
}).then(() => {
|
||||
called++;
|
||||
})
|
||||
)xyz123");
|
||||
auto result = runtime_->global()
|
||||
.getPropertyAsFunction(*runtime_, "getResult")
|
||||
.call(*runtime_);
|
||||
EXPECT_EQ(result.getNumber(), 2);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testSetImmediate) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
eval(R"xyz123(
|
||||
let called = false;
|
||||
function getResult() {
|
||||
return called;
|
||||
}
|
||||
setImmediate(() => {
|
||||
called = true;
|
||||
});
|
||||
)xyz123");
|
||||
auto result = runtime_->global()
|
||||
.getPropertyAsFunction(*runtime_, "getResult")
|
||||
.call(*runtime_);
|
||||
EXPECT_EQ(result.getBool(), true);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testNestedSetImmediate) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
eval(R"xyz123(
|
||||
let called = false;
|
||||
function getResult() {
|
||||
return called;
|
||||
}
|
||||
setImmediate(() => {
|
||||
setImmediate(() => {
|
||||
called = true;
|
||||
})
|
||||
});
|
||||
)xyz123");
|
||||
auto result = runtime_->global()
|
||||
.getPropertyAsFunction(*runtime_, "getResult")
|
||||
.call(*runtime_);
|
||||
EXPECT_EQ(result.getBool(), true);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testSetImmediateWithInvalidArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("setImmediate();"),
|
||||
"setImmediate must be called with at least one argument (a function to call)");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("setImmediate('invalid');"),
|
||||
"The first argument to setImmediate must be a function.");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("setImmediate({});"),
|
||||
"The first argument to setImmediate must be a function.");
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testClearImmediate) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
eval(R"xyz123(
|
||||
let called = false;
|
||||
function getResult() {
|
||||
return called;
|
||||
}
|
||||
const handle = setImmediate(() => {
|
||||
called = true;
|
||||
});
|
||||
clearImmediate(handle);
|
||||
)xyz123");
|
||||
|
||||
auto func = runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
|
||||
auto val = func.call(*runtime_);
|
||||
EXPECT_EQ(val.getBool(), false);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testClearImmediateWithInvalidHandle) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
auto js = R"xyz123(
|
||||
let called = false;
|
||||
const handle = setImmediate(() => {
|
||||
called = true;
|
||||
});
|
||||
function getResult() {
|
||||
return called;
|
||||
}
|
||||
function clearInvalidHandle() {
|
||||
clearImmediate(handle);
|
||||
}
|
||||
)xyz123";
|
||||
eval(js);
|
||||
|
||||
auto clear =
|
||||
runtime_->global().getPropertyAsFunction(*runtime_, "clearInvalidHandle");
|
||||
// Clearing an invalid handle should fail silently.
|
||||
EXPECT_NO_THROW(clear.call((*runtime_)));
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testClearImmediateWithInvalidArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
eval("clearImmediate();");
|
||||
expectNoError();
|
||||
|
||||
eval("clearImmediate('invalid');");
|
||||
expectNoError();
|
||||
|
||||
eval("clearImmediate({});");
|
||||
expectNoError();
|
||||
|
||||
eval("clearImmediate(undefined);");
|
||||
expectNoError();
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testSetTimeout) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createTimer(_, 100))
|
||||
.WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
let called = false;
|
||||
setTimeout(() => {
|
||||
called = true;
|
||||
}, 100);
|
||||
function getResult() {
|
||||
return called;
|
||||
}
|
||||
)xyz123");
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
auto called = runtime_->global()
|
||||
.getPropertyAsFunction(*runtime_, "getResult")
|
||||
.call(*runtime_);
|
||||
EXPECT_EQ(called.getBool(), true);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testSetTimeoutWithoutDelay) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
EXPECT_CALL(
|
||||
*mockRegistry_,
|
||||
createTimer(_, 0)); // If delay is not provided, it should use 0
|
||||
eval("setTimeout(() => {});");
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testSetTimeoutWithPassThroughArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createTimer(_, 0)).WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
let result;
|
||||
setTimeout(arg => {
|
||||
result = arg;
|
||||
}, undefined, 'foo');
|
||||
function getResult() {
|
||||
return result;
|
||||
}
|
||||
)xyz123");
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
auto result = runtime_->global()
|
||||
.getPropertyAsFunction(*runtime_, "getResult")
|
||||
.call(*runtime_);
|
||||
EXPECT_EQ(result.asString(*runtime_).utf8(*runtime_), "foo");
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testSetTimeoutWithInvalidArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("setTimeout();"),
|
||||
"setTimeout must be called with at least one argument (the function to call).");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("setTimeout('invalid');"),
|
||||
"The first argument to setTimeout must be a function.");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("setTimeout(() => {}, 'invalid');"),
|
||||
"The second argument to setTimeout must be a number or undefined.");
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testClearTimeout) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createTimer(_, 100))
|
||||
.WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
const handle = setTimeout(() => {}, 100);
|
||||
function clear() {
|
||||
clearTimeout(handle);
|
||||
}
|
||||
)xyz123");
|
||||
EXPECT_CALL(*mockRegistry_, deleteTimer(timerID));
|
||||
runtime_->global().getPropertyAsFunction(*runtime_, "clear").call(*runtime_);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testClearTimeoutWithInvalidArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
eval("clearTimeout();");
|
||||
expectNoError();
|
||||
|
||||
eval("clearTimeout('invalid');");
|
||||
expectNoError();
|
||||
|
||||
eval("clearTimeout({});");
|
||||
expectNoError();
|
||||
|
||||
eval("clearTimeout(undefined);");
|
||||
expectNoError();
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testClearTimeoutForExpiredTimer) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createTimer(_, 100))
|
||||
.WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
const handle = setTimeout(() => {}, 100);
|
||||
function clear() {
|
||||
clearTimeout(handle);
|
||||
}
|
||||
)xyz123");
|
||||
// Call the timer
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
|
||||
// Now clear the called timer
|
||||
EXPECT_CALL(*mockRegistry_, deleteTimer(timerID));
|
||||
auto clear = runtime_->global().getPropertyAsFunction(*runtime_, "clear");
|
||||
EXPECT_NO_THROW(clear.call(*runtime_));
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testSetInterval) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createRecurringTimer(_, 100))
|
||||
.WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
let result = 0;
|
||||
setInterval(() => {
|
||||
result++;
|
||||
}, 100);
|
||||
function getResult() {
|
||||
return result;
|
||||
}
|
||||
)xyz123");
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
auto getResult =
|
||||
runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
|
||||
EXPECT_EQ(getResult.call(*runtime_).asNumber(), 1.0);
|
||||
|
||||
// Should be able to call the same callback again.
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
EXPECT_EQ(getResult.call(*runtime_).asNumber(), 2.0);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testSetIntervalWithPassThroughArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createRecurringTimer(_, 100))
|
||||
.WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
let result;
|
||||
setInterval(arg => {
|
||||
result = arg;
|
||||
}, 100, 'foo');
|
||||
function getResult() {
|
||||
return result;
|
||||
}
|
||||
)xyz123");
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
|
||||
auto getResult =
|
||||
runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
|
||||
EXPECT_EQ(
|
||||
getResult.call(*runtime_).asString(*runtime_).utf8(*runtime_), "foo");
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testSetIntervalWithInvalidArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("setInterval();"),
|
||||
"setInterval must be called with at least two arguments (the function to call and the delay).");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("setInterval(() => {});"),
|
||||
"setInterval must be called with at least two arguments (the function to call and the delay).");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("setInterval('invalid', 100);"),
|
||||
"The first argument to setInterval must be a function.");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("setInterval(() => {}, 'invalid');"),
|
||||
"The second argument to setInterval must be a number.");
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testClearInterval) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createRecurringTimer(_, 100))
|
||||
.WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
let result = 0;
|
||||
const handle = setInterval(() => {
|
||||
result++;
|
||||
}, 100);
|
||||
function clear() {
|
||||
clearInterval(handle);
|
||||
}
|
||||
function getResult() {
|
||||
return result;
|
||||
}
|
||||
)xyz123");
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
auto getResult =
|
||||
runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
|
||||
EXPECT_EQ(getResult.call(*runtime_).asNumber(), 1.0);
|
||||
|
||||
EXPECT_CALL(*mockRegistry_, deleteTimer(timerID));
|
||||
runtime_->global().getPropertyAsFunction(*runtime_, "clear").call(*runtime_);
|
||||
step();
|
||||
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
// Callback should not have been invoked again.
|
||||
EXPECT_EQ(getResult.call(*runtime_).asNumber(), 1.0);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testClearIntervalWithInvalidArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
eval("clearInterval();");
|
||||
expectNoError();
|
||||
|
||||
eval("clearInterval(false);");
|
||||
expectNoError();
|
||||
|
||||
eval("clearInterval({});");
|
||||
expectNoError();
|
||||
|
||||
eval("clearInterval(undefined);");
|
||||
expectNoError();
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testRequestAnimationFrame) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createTimer(_, 0)).WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
let called = false;
|
||||
performance = {
|
||||
now: () => 0
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
called = true;
|
||||
});
|
||||
function getResult() {
|
||||
return called;
|
||||
}
|
||||
)xyz123");
|
||||
auto getResult =
|
||||
runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
|
||||
EXPECT_EQ(getResult.call(*runtime_).getBool(), false);
|
||||
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
EXPECT_EQ(getResult.call(*runtime_).getBool(), true);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
ReactInstanceTest,
|
||||
testRequestAnimationFrameCallbackArgIsPerformanceNow) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createTimer(_, 0)).WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
let now = 0;
|
||||
performance = {
|
||||
now: () => 123456
|
||||
}
|
||||
requestAnimationFrame(($now) => {
|
||||
now = $now;
|
||||
});
|
||||
function getResult() {
|
||||
return now;
|
||||
}
|
||||
)xyz123");
|
||||
auto getResult =
|
||||
runtime_->global().getPropertyAsFunction(*runtime_, "getResult");
|
||||
EXPECT_EQ(getResult.call(*runtime_).getNumber(), 0);
|
||||
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
EXPECT_EQ(getResult.call(*runtime_).getNumber(), 123456);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testRequestAnimationFrameWithInvalidArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
eval(R"xyz123(
|
||||
performance = {
|
||||
now: () => 0
|
||||
}
|
||||
)xyz123");
|
||||
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("requestAnimationFrame();"),
|
||||
"requestAnimationFrame must be called with at least one argument (i.e: a callback)");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("requestAnimationFrame('invalid');"),
|
||||
"The first argument to requestAnimationFrame must be a function.");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("requestAnimationFrame({});"),
|
||||
"The first argument to requestAnimationFrame must be a function.");
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testCancelAnimationFrame) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createTimer(_, 0)).WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
let called = false;
|
||||
performance = {
|
||||
now: () => 0
|
||||
}
|
||||
const handle = requestAnimationFrame(() => {
|
||||
called = true;
|
||||
});
|
||||
function clear() {
|
||||
cancelAnimationFrame(handle);
|
||||
}
|
||||
function getResult() {
|
||||
return called;
|
||||
}
|
||||
)xyz123");
|
||||
|
||||
EXPECT_CALL(*mockRegistry_, deleteTimer(timerID));
|
||||
runtime_->global().getPropertyAsFunction(*runtime_, "clear").call(*runtime_);
|
||||
|
||||
// Attempt to call timer; should fail silently.
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
|
||||
// Verify the callback was not called.
|
||||
auto called = runtime_->global()
|
||||
.getPropertyAsFunction(*runtime_, "getResult")
|
||||
.call(*runtime_);
|
||||
EXPECT_EQ(called.getBool(), false);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testCancelAnimationFrameWithInvalidArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
eval("cancelAnimationFrame();");
|
||||
expectNoError();
|
||||
|
||||
eval("cancelAnimationFrame(false);");
|
||||
expectNoError();
|
||||
|
||||
eval("cancelAnimationFrame({});");
|
||||
expectNoError();
|
||||
|
||||
eval("cancelAnimationFrame(undefined);");
|
||||
expectNoError();
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testCancelAnimationFrameWithExpiredTimer) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
uint32_t timerID{0};
|
||||
EXPECT_CALL(*mockRegistry_, createTimer(_, 0)).WillOnce(SaveArg<0>(&timerID));
|
||||
eval(R"xyz123(
|
||||
let called = false;
|
||||
performance = {
|
||||
now: () => 0
|
||||
}
|
||||
const handle = requestAnimationFrame(() => {
|
||||
called = true;
|
||||
});
|
||||
function clear() {
|
||||
cancelAnimationFrame(handle);
|
||||
}
|
||||
function getResult() {
|
||||
return called;
|
||||
}
|
||||
)xyz123");
|
||||
|
||||
timerManager_->callTimer(timerID);
|
||||
step();
|
||||
|
||||
auto called = runtime_->global()
|
||||
.getPropertyAsFunction(*runtime_, "getResult")
|
||||
.call(*runtime_);
|
||||
EXPECT_EQ(called.getBool(), true);
|
||||
|
||||
EXPECT_CALL(*mockRegistry_, deleteTimer(timerID));
|
||||
auto clear = runtime_->global().getPropertyAsFunction(*runtime_, "clear");
|
||||
// Canceling an expired timer should fail silently.
|
||||
EXPECT_NO_THROW(clear.call(*runtime_));
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testRegisterCallableModule) {
|
||||
initializeRuntimeWithScript(R"xyz123(
|
||||
let called = false;
|
||||
const module = {
|
||||
bar: () => {
|
||||
called = true;
|
||||
},
|
||||
};
|
||||
function getResult() {
|
||||
return called;
|
||||
}
|
||||
RN$registerCallableModule('foo', () => module);
|
||||
)xyz123");
|
||||
|
||||
auto args = folly::dynamic::array(0);
|
||||
instance_->callFunctionOnModule("foo", "bar", std::move(args));
|
||||
step();
|
||||
|
||||
auto called = runtime_->global()
|
||||
.getPropertyAsFunction(*runtime_, "getResult")
|
||||
.call(*runtime_);
|
||||
EXPECT_EQ(called.getBool(), true);
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testRegisterCallableModule_invalidArgs) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("RN$registerCallableModule();"),
|
||||
"registerCallableModule requires exactly 2 arguments");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("RN$registerCallableModule('foo');"),
|
||||
"registerCallableModule requires exactly 2 arguments");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("RN$registerCallableModule(1, () => ({}));"),
|
||||
"The first argument to registerCallableModule must be a string (the name of the JS module).");
|
||||
EXPECT_EQ(
|
||||
getErrorMessage("RN$registerCallableModule('foo', false);"),
|
||||
"The second argument to registerCallableModule must be a function that returns the JS module.");
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testCallFunctionOnModule_invalidModule) {
|
||||
initializeRuntimeWithScript("");
|
||||
|
||||
auto args = folly::dynamic::array(0);
|
||||
instance_->callFunctionOnModule("invalidModule", "method", std::move(args));
|
||||
step();
|
||||
expectError();
|
||||
EXPECT_EQ(
|
||||
getLastErrorMessage(),
|
||||
"Failed to call into JavaScript module method invalidModule.method(). Module has not been registered as callable. Registered callable JavaScript modules (n = 0):. Did you forget to call `RN$registerCallableModule`?");
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testCallFunctionOnModule_undefinedMethod) {
|
||||
initializeRuntimeWithScript(R"xyz123(
|
||||
const module = {
|
||||
bar: () => {},
|
||||
};
|
||||
RN$registerCallableModule('foo', () => module);
|
||||
)xyz123");
|
||||
|
||||
auto args = folly::dynamic::array(0);
|
||||
instance_->callFunctionOnModule("foo", "invalidMethod", std::move(args));
|
||||
step();
|
||||
expectError();
|
||||
EXPECT_EQ(
|
||||
getLastErrorMessage(),
|
||||
"Failed to call into JavaScript module method foo.invalidMethod. Module exists, but the method is undefined.");
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testCallFunctionOnModule_invalidMethod) {
|
||||
initializeRuntimeWithScript(R"xyz123(
|
||||
const module = {
|
||||
bar: false,
|
||||
};
|
||||
RN$registerCallableModule('foo', () => module);
|
||||
)xyz123");
|
||||
|
||||
auto args = folly::dynamic::array(0);
|
||||
instance_->callFunctionOnModule("foo", "bar", std::move(args));
|
||||
step();
|
||||
expectError();
|
||||
}
|
||||
|
||||
TEST_F(ReactInstanceTest, testRegisterCallableModule_withArgs) {
|
||||
initializeRuntimeWithScript(R"xyz123(
|
||||
let result;
|
||||
const module = {
|
||||
bar: thing => {
|
||||
result = thing;
|
||||
},
|
||||
};
|
||||
RN$registerCallableModule('foo', () => module);
|
||||
function getResult() {
|
||||
return result;
|
||||
}
|
||||
)xyz123");
|
||||
|
||||
auto args = folly::dynamic::array(1);
|
||||
instance_->callFunctionOnModule("foo", "bar", std::move(args));
|
||||
step();
|
||||
|
||||
auto result = runtime_->global()
|
||||
.getPropertyAsFunction(*runtime_, "getResult")
|
||||
.call(*runtime_);
|
||||
EXPECT_EQ(result.getNumber(), 1);
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
Загрузка…
Ссылка в новой задаче