diff --git a/packages/react-native/ReactCommon/react/runtime/TimerManager.cpp b/packages/react-native/ReactCommon/react/runtime/TimerManager.cpp index d8b33ddb648..16ae84d1a47 100644 --- a/packages/react-native/ReactCommon/react/runtime/TimerManager.cpp +++ b/packages/react-native/ReactCommon/react/runtime/TimerManager.cpp @@ -21,125 +21,134 @@ void TimerManager::setRuntimeExecutor( runtimeExecutor_ = runtimeExecutor; } -std::shared_ptr TimerManager::createReactNativeMicrotask( +TimerHandle 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); + TimerHandle timerID = timerIndex_++; + timers_.emplace( + std::piecewise_construct, + std::forward_as_tuple(timerID), + std::forward_as_tuple( + std::move(callback), std::move(args), /* repeat */ false)); reactNativeMicrotasksQueue_.push_back(timerID); - return std::make_shared(timerID); + return timerID; } void TimerManager::callReactNativeMicrotasks(jsi::Runtime& runtime) { - std::vector reactNativeMicrotasksQueue; + 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); + auto it = timers_.find(reactNativeMicrotaskID); + if (it != timers_.end()) { + it->second.invoke(runtime); + timers_.erase(it); } } } } -std::shared_ptr TimerManager::createTimer( +TimerHandle 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); + TimerHandle timerID = timerIndex_++; + timers_.emplace( + std::piecewise_construct, + std::forward_as_tuple(timerID), + std::forward_as_tuple( + std::move(callback), std::move(args), /* repeat */ false)); platformTimerRegistry_->createTimer(timerID, delay); - return std::make_shared(timerID); + return timerID; } -std::shared_ptr TimerManager::createRecurringTimer( +TimerHandle 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); + TimerHandle timerID = timerIndex_++; + timers_.emplace( + std::piecewise_construct, + std::forward_as_tuple(timerID), + std::forward_as_tuple( + std::move(callback), std::move(args), /* repeat */ true)); platformTimerRegistry_->createRecurringTimer(timerID, delay); - return std::make_shared(timerID); + return timerID; } void TimerManager::deleteReactNativeMicrotask( jsi::Runtime& runtime, - std::shared_ptr timerHandle) { - if (timerHandle == nullptr) { + TimerHandle timerHandle) { + if (timerHandle < 0) { 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; - } + auto it = std::find( + reactNativeMicrotasksQueue_.begin(), + reactNativeMicrotasksQueue_.end(), + timerHandle); + if (it != reactNativeMicrotasksQueue_.end()) { + reactNativeMicrotasksQueue_.erase(it); } - if (timers_.find(timerHandle->index()) != timers_.end()) { - timers_.erase(timerHandle->index()); + if (timers_.find(timerHandle) != timers_.end()) { + timers_.erase(timerHandle); } } -void TimerManager::deleteTimer( - jsi::Runtime& runtime, - std::shared_ptr timerHandle) { - if (timerHandle == nullptr) { +void TimerManager::deleteTimer(jsi::Runtime& runtime, TimerHandle timerHandle) { + if (timerHandle < 0) { 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()); + platformTimerRegistry_->deleteTimer(timerHandle); + if (timers_.find(timerHandle) != timers_.end()) { + timers_.erase(timerHandle); } } void TimerManager::deleteRecurringTimer( jsi::Runtime& runtime, - std::shared_ptr timerHandle) { - if (timerHandle == nullptr) { + TimerHandle timerHandle) { + if (timerHandle < 0) { 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()); + platformTimerRegistry_->deleteTimer(timerHandle); + + auto it = timers_.find(timerHandle); + if (it != timers_.end()) { + timers_.erase(it); } } -void TimerManager::callTimer(uint32_t timerID) { - runtimeExecutor_([this, timerID](jsi::Runtime& runtime) { +void TimerManager::callTimer(TimerHandle timerHandle) { + runtimeExecutor_([this, timerHandle](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); + auto it = timers_.find(timerHandle); + if (it != timers_.end()) { + bool repeats = it->second.repeat; + it->second.invoke(runtime); + + if (!repeats) { + // Invoking a timer has the potential to delete it. Double check the + // timer still exists before accessing it again. + it = timers_.find(timerHandle); + if (it != timers_.end()) { + timers_.erase(it); + } } } }); @@ -180,9 +189,8 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { moreArgs.emplace_back(rt, args[extraArgNum]); } - auto handle = createReactNativeMicrotask( + return createReactNativeMicrotask( std::move(callback), std::move(moreArgs)); - return jsi::Object::createFromHostObject(rt, handle); })); runtime.global().setProperty( @@ -197,13 +205,10 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { 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(); + if (count > 0 && args[0].isNumber()) { + auto handle = (TimerHandle)args[0].asNumber(); + deleteReactNativeMicrotask(rt, handle); } - std::shared_ptr handle = - args[0].asObject(rt).asHostObject(rt); - deleteReactNativeMicrotask(rt, handle); return jsi::Value::undefined(); })); @@ -245,9 +250,7 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { moreArgs.emplace_back(rt, args[extraArgNum]); } - auto handle = - createTimer(std::move(callback), std::move(moreArgs), delay); - return jsi::Object::createFromHostObject(rt, handle); + return createTimer(std::move(callback), std::move(moreArgs), delay); })); runtime.global().setProperty( @@ -262,13 +265,10 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { 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(); + if (count > 0 && args[0].isNumber()) { + auto handle = (TimerHandle)args[0].asNumber(); + deleteTimer(rt, handle); } - std::shared_ptr host = - args[0].asObject(rt).asHostObject(rt); - deleteTimer(rt, host); return jsi::Value::undefined(); })); @@ -284,10 +284,10 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { const jsi::Value& thisVal, const jsi::Value* args, size_t count) { - if (count < 2) { + if (count == 0) { throw jsi::JSError( rt, - "setInterval must be called with at least two arguments (the function to call and the delay)."); + "setInterval must be called with at least one argument (the function to call)."); } if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) { @@ -295,12 +295,8 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { 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(); + auto delay = + count > 1 && args[1].isNumber() ? args[1].getNumber() : 0; // Package up the remaining argument values into one place. std::vector moreArgs; @@ -308,9 +304,8 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { moreArgs.emplace_back(rt, args[extraArgNum]); } - auto handle = createRecurringTimer( + return createRecurringTimer( std::move(callback), std::move(moreArgs), delay); - return jsi::Object::createFromHostObject(rt, handle); })); runtime.global().setProperty( @@ -325,13 +320,10 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { 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(); + if (count > 0 && args[0].isNumber()) { + auto handle = (TimerHandle)args[0].asNumber(); + deleteRecurringTimer(rt, handle); } - std::shared_ptr host = - args[0].asObject(rt).asHostObject(rt); - deleteRecurringTimer(rt, host); return jsi::Value::undefined(); })); @@ -359,12 +351,11 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { "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( + [callbackContainer = std::make_shared( args[0].getObject(rt).getFunction(rt))]( jsi::Runtime& rt, const jsi::Value& thisVal, @@ -374,19 +365,16 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { 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)}); + return 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( + return createTimer( std::move(callback), std::vector(), /* delay */ 0); - return jsi::Object::createFromHostObject(rt, handle); })); runtime.global().setProperty( @@ -401,13 +389,10 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { 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(); + if (count > 0 && args[0].isNumber()) { + auto handle = (TimerHandle)args[0].asNumber(); + deleteTimer(rt, handle); } - std::shared_ptr host = - args[0].asObject(rt).asHostObject(rt); - deleteTimer(rt, host); return jsi::Value::undefined(); })); } diff --git a/packages/react-native/ReactCommon/react/runtime/TimerManager.h b/packages/react-native/ReactCommon/react/runtime/TimerManager.h index 49e54684d49..9743087e80c 100644 --- a/packages/react-native/ReactCommon/react/runtime/TimerManager.h +++ b/packages/react-native/ReactCommon/react/runtime/TimerManager.h @@ -16,31 +16,12 @@ namespace facebook::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_; -}; +using TimerHandle = int; /* * 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 args, @@ -67,49 +48,45 @@ class TimerManager { void callReactNativeMicrotasks(jsi::Runtime& runtime); - void callTimer(uint32_t); + void callTimer(TimerHandle handle); void attachGlobals(jsi::Runtime& runtime); private: - std::shared_ptr createReactNativeMicrotask( + TimerHandle createReactNativeMicrotask( jsi::Function&& callback, std::vector&& args); - void deleteReactNativeMicrotask( - jsi::Runtime& runtime, - std::shared_ptr handle); + void deleteReactNativeMicrotask(jsi::Runtime& runtime, TimerHandle handle); - std::shared_ptr createTimer( + TimerHandle createTimer( jsi::Function&& callback, std::vector&& args, double delay); - void deleteTimer(jsi::Runtime& runtime, std::shared_ptr handle); + void deleteTimer(jsi::Runtime& runtime, TimerHandle handle); - std::shared_ptr createRecurringTimer( + TimerHandle createRecurringTimer( jsi::Function&& callback, std::vector&& args, double delay); - void deleteRecurringTimer( - jsi::Runtime& runtime, - std::shared_ptr handle); + void deleteRecurringTimer(jsi::Runtime& runtime, TimerHandle handle); RuntimeExecutor runtimeExecutor_; std::unique_ptr platformTimerRegistry_; // A map (id => callback func) of the currently active JS timers - std::unordered_map> timers_; + std::unordered_map timers_; // Each timeout that is registered on this queue gets a sequential id. This // is the global count from which those are assigned. - uint32_t timerIndex_{0}; + TimerHandle 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 reactNativeMicrotasksQueue_; + std::vector reactNativeMicrotasksQueue_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp b/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp index db2ec9255bf..9ba76917999 100644 --- a/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp +++ b/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp @@ -537,16 +537,10 @@ TEST_F(ReactInstanceTest, testSetIntervalWithInvalidArgs) { 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)."); + "setInterval must be called with at least one argument (the function to call)."); 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) {