diff --git a/packages/react-native/ReactCommon/react/runtime/TimerManager.cpp b/packages/react-native/ReactCommon/react/runtime/TimerManager.cpp index 5514a268370..da81670bb45 100644 --- a/packages/react-native/ReactCommon/react/runtime/TimerManager.cpp +++ b/packages/react-native/ReactCommon/react/runtime/TimerManager.cpp @@ -300,8 +300,9 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { } if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) { - // Do not throw any error to match web spec - return timerIndex_++; + // Do not throw any error to match web spec; instead return 0, an + // invalid timer id + return 0; } auto callback = args[0].getObject(rt).getFunction(rt); @@ -358,8 +359,9 @@ void TimerManager::attachGlobals(jsi::Runtime& runtime) { } if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) { - throw jsi::JSError( - rt, "The first argument to setInterval must be a function."); + // Do not throw any error to match web spec; instead return 0, an + // invalid timer id + return 0; } auto callback = args[0].getObject(rt).getFunction(rt); auto delay = count > 1 diff --git a/packages/react-native/ReactCommon/react/runtime/TimerManager.h b/packages/react-native/ReactCommon/react/runtime/TimerManager.h index 879ba30430a..914dec0b167 100644 --- a/packages/react-native/ReactCommon/react/runtime/TimerManager.h +++ b/packages/react-native/ReactCommon/react/runtime/TimerManager.h @@ -93,7 +93,9 @@ class TimerManager { // Each timeout that is registered on this queue gets a sequential id. This // is the global count from which those are assigned. - TimerHandle timerIndex_{0}; + // As per WHATWG HTML 8.6.1 (Timers) ids must be greater than zero, i.e. start + // at 1 + TimerHandle timerIndex_{1}; // The React Native microtask queue is used to back public APIs including // `queueMicrotask`, `clearImmediate`, and `setImmediate` (which is used by 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 6e93d7a2b8a..63b5aa4a385 100644 --- a/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp +++ b/packages/react-native/ReactCommon/react/runtime/tests/cxx/ReactInstanceTest.cpp @@ -267,7 +267,9 @@ TEST_F(ReactInstanceTest, testSetTimeoutWithoutDelay) { EXPECT_CALL( *mockRegistry_, createTimer(_, 0)); // If delay is not provided, it should use 0 - eval("setTimeout(() => {});"); + auto val = eval("setTimeout(() => {});"); + expectNoError(); + EXPECT_EQ(val.asNumber(), 1); // First timer id should start at 1 } TEST_F(ReactInstanceTest, testSetTimeoutWithPassThroughArgs) { @@ -299,8 +301,9 @@ TEST_F(ReactInstanceTest, testSetTimeoutWithInvalidArgs) { getErrorMessage("setTimeout();"), "setTimeout must be called with at least one argument (the function to call)."); - eval("setTimeout('invalid');"); + auto val = eval("setTimeout('invalid')"); expectNoError(); + EXPECT_EQ(val.asNumber(), 0); eval("setTimeout(() => {}, 'invalid');"); expectNoError(); @@ -417,9 +420,10 @@ TEST_F(ReactInstanceTest, testSetIntervalWithInvalidArgs) { EXPECT_EQ( getErrorMessage("setInterval();"), "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."); + + auto val = eval("setInterval('invalid', 100)"); + expectNoError(); + EXPECT_EQ(val.asNumber(), 0); } TEST_F(ReactInstanceTest, testClearInterval) {