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:
Lulu Wu 2023-04-18 11:01:12 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 94356e14ec
Коммит e2e59c4d0e
10 изменённых файлов: 2087 добавлений и 0 удалений

Просмотреть файл

@ -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