Skip to content
76 changes: 26 additions & 50 deletions packages/react-native/ReactCommon/react/runtime/TimerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,49 +258,36 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) {
}

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) {
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).");
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);
auto callback = args[0].isObject() && args[0].asObject(rt).isFunction(rt)
? args[0].getObject(rt).getFunction(rt)
: jsi::Function::createFromHostFunction(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a host function for invalid calls seems wasteful and I don't think it's necessary. I saw that Chrome returns 0 for some types of calls to setTimeout (e.g.: setTimeout('')) so we could do that same here (just return jsi::Value(0).

You need to make another change to make that work though. In TimerManager.h we initialize the value with 0 (as the initial timer handle) and we should change that to 1, so the first timer doesn't conflict with an "invalid" one. When you do that, might be worth renaming timerIndex_ as nextTimerHandle_ for clarity.

This is another aspect where we'll align with the spec: https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timer-initialisation-steps (the handle must be a value greater than 0, but we can use 0 for the invalid ones).

rt,
jsi::PropNameID::forAscii(rt, "dummyFunction"),
0,
[](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) {
return jsi::Value::undefined();
});

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;
auto delay = (count > 1 && args[1].isNumber()) ? args[1].getNumber() : 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also check for delay < 0 and assign 0 in that case as well. You can use std::max(args[1].getNumber(), 0) in this same expression :)


// 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]);
moreArgs.emplace_back(rt, args[extraArgNum]);
}

return createTimer(
std::move(callback),
std::move(moreArgs),
delay,
TimerSource::SetTimeout);
}));
return createTimer(std::move(callback), std::move(moreArgs), delay);
}));

runtime.global().setProperty(
runtime,
Expand Down Expand Up @@ -333,26 +320,15 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) {
const jsi::Value& thisVal,
const jsi::Value* args,
size_t count) {
if (count == 0) {
throw jsi::JSError(
rt,
"setInterval must be called with at least one argument (the function to call).");
}

if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) {
if (count == 0 || !args[0].isObject() ||
!args[0].asObject(rt).isFunction(rt)) {
throw jsi::JSError(
rt, "The first argument to setInterval must be a function.");
rt, "setInterval must be called with a function.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setInterval should behave the same way as setTimeout. We shouldn't fail if the value isn't a function but just return early with a 0 handle.

}
auto callback = args[0].getObject(rt).getFunction(rt);
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]);
}

(count > 1 && args[1].isNumber()) ? args[1].getNumber() : 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same about delay being greater or equal to zero.

std::vector<jsi::Value> moreArgs(args + 2, args + count);
return createRecurringTimer(
std::move(callback),
std::move(moreArgs),
Expand Down