/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "TimerManager.h" #include #include namespace facebook::react { TimerManager::TimerManager( std::unique_ptr platformTimerRegistry) noexcept : platformTimerRegistry_(std::move(platformTimerRegistry)) {} void TimerManager::setRuntimeExecutor( RuntimeExecutor runtimeExecutor) noexcept { runtimeExecutor_ = runtimeExecutor; } std::shared_ptr TimerManager::createReactNativeMicrotask( jsi::Function&& callback, std::vector&& args) { auto sharedCallback = std::make_shared( 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(timerID); } void TimerManager::callReactNativeMicrotasks(jsi::Runtime& runtime) { std::vector 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 TimerManager::createTimer( jsi::Function&& callback, std::vector&& args, double delay) { auto sharedCallback = std::make_shared( 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(timerID); } std::shared_ptr TimerManager::createRecurringTimer( jsi::Function&& callback, std::vector&& args, double delay) { auto sharedCallback = std::make_shared( 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(timerID); } void TimerManager::deleteReactNativeMicrotask( jsi::Runtime& runtime, std::shared_ptr 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) { 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) { 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 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(rt)) { return jsi::Value::undefined(); } std::shared_ptr handle = args[0].asObject(rt).asHostObject(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 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(rt)) { return jsi::Value::undefined(); } std::shared_ptr host = args[0].asObject(rt).asHostObject(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 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(rt)) { return jsi::Value::undefined(); } std::shared_ptr host = args[0].asObject(rt).asHostObject(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; auto callback = jsi::Function::createFromHostFunction( rt, jsi::PropNameID::forAscii(rt, "RN$rafFn"), 0, [callbackContainer = std::make_shared( 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(), /* 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(rt)) { return jsi::Value::undefined(); } std::shared_ptr host = args[0].asObject(rt).asHostObject(rt); deleteTimer(rt, host); return jsi::Value::undefined(); })); } } // namespace facebook::react