1 /**
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "timer.h"
17
18 #include <array>
19 #include <unordered_map>
20 #include <iostream>
21
22 #if defined(__clang__)
23 #pragma clang diagnostic push
24 #pragma clang diagnostic ignored "-Wshadow"
25 #elif defined(__GNUC__)
26 #pragma GCC diagnostic push
27 #pragma GCC diagnostic ignored "-Wshadow"
28 #endif
29
30 // NOLINTBEGIN(readability-identifier-naming, readability-redundant-declaration)
31 // CC-OFFNXT(G.FMT.10-CPP) project code style
32 napi_status __attribute__((weak)) napi_is_callable(napi_env env, napi_value value, bool *result);
33 // CC-OFFNXT(G.FMT.10-CPP) project code style
34 napi_status __attribute__((weak)) napi_get_uv_event_loop(napi_env env, struct uv_loop_s **loop);
35 // NOLINTEND(readability-identifier-naming, readability-redundant-declaration)
36
37 namespace ark::ets::interop::js::helper {
38
39 // NOLINTBEGIN(fuchsia-statically-constructed-objects)
40 // CC-OFFNXT(G.NAM.03-CPP) project code style
41 static uint32_t g_nextTimerId = 0;
42 static std::unordered_map<uint32_t, TimerInfo *> g_timers;
43 // NOLINTEND(fuchsia-statically-constructed-objects)
44
TimerInfo(napi_env env,napi_ref cb,std::vector<napi_ref> cbArgs,bool repeat)45 TimerInfo::TimerInfo(napi_env env, napi_ref cb, std::vector<napi_ref> cbArgs, bool repeat)
46 : env(env), cb(cb), cbArgs(std::move(cbArgs)), repeat(repeat), timer(new uv_timer_t())
47 {
48 timerId = g_nextTimerId++;
49 g_timers[timerId] = this;
50 timer->data = this;
51 uv_loop_t *loop = nullptr;
52 napi_get_uv_event_loop(env, &loop);
53 uv_timer_init(loop, timer);
54 }
55
~TimerInfo()56 TimerInfo::~TimerInfo()
57 {
58 napi_delete_reference(env, cb);
59 for (auto &ref : cbArgs) {
60 napi_delete_reference(env, ref);
61 }
62 g_timers.erase(timerId);
63 uv_timer_stop(timer);
64 uv_close(reinterpret_cast<uv_handle_t *>(timer), TIMER_CLOSE_CALLBACK);
65 }
66
RegisterTimer(napi_env env,napi_ref cb,std::vector<napi_ref> cbArgs,int32_t timeout,bool repeat)67 static napi_value RegisterTimer(napi_env env, napi_ref cb, std::vector<napi_ref> cbArgs, int32_t timeout, bool repeat)
68 {
69 auto timerCallback = [](uv_timer_t *timer) {
70 auto *timerInfo = reinterpret_cast<TimerInfo *>(timer->data);
71 napi_env env = timerInfo->env;
72 napi_value callback = nullptr;
73 napi_get_reference_value(env, timerInfo->cb, &callback);
74 std::vector<napi_value> callbackArgs(timerInfo->cbArgs.size());
75 for (auto argIdx = 0U; argIdx < callbackArgs.size(); ++argIdx) {
76 napi_get_reference_value(env, timerInfo->cbArgs[argIdx], &callbackArgs[argIdx]);
77 }
78
79 auto timerId = timerInfo->timerId;
80 napi_value undefined = nullptr;
81 napi_get_undefined(env, &undefined);
82
83 napi_value result = nullptr;
84 napi_call_function(env, undefined, callback, callbackArgs.size(), callbackArgs.data(), &result);
85
86 bool isExceptionPending = false;
87 napi_is_exception_pending(env, &isExceptionPending);
88 if (isExceptionPending || (result == nullptr)) {
89 std::cerr << "Error occured during timer callback";
90 std::abort(); // CC-OFF(G.STD.16-CPP) fatal error
91 }
92
93 if (g_timers.find(timerId) == g_timers.end()) {
94 // timer was cleared during callback
95 return;
96 }
97
98 if (timerInfo->repeat) {
99 uv_timer_again(timer);
100 } else {
101 delete timerInfo;
102 }
103 };
104
105 uv_loop_t *loop = nullptr;
106 napi_get_uv_event_loop(env, &loop);
107
108 auto *timerInfo = new TimerInfo(env, cb, std::move(cbArgs), repeat);
109
110 uv_update_time(loop);
111 uv_timer_start(timerInfo->timer, timerCallback, timeout, timeout > 0 ? timeout : 1);
112 uv_async_send(&loop->wq_async);
113
114 napi_value timerId = nullptr;
115 napi_create_uint32(env, timerInfo->timerId, &timerId);
116 return timerId;
117 }
118
SetTimeoutImpl(napi_env env,napi_callback_info info,bool repeat)119 napi_value SetTimeoutImpl(napi_env env, napi_callback_info info, bool repeat)
120 {
121 size_t argc = 0;
122 napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr);
123 if (argc < 1) {
124 napi_throw_error(env, nullptr, "setTimeout/setInterval: callback info is nullptr");
125 return nullptr;
126 }
127
128 std::vector<napi_value> argv(argc);
129 napi_value thisVar = nullptr;
130 napi_get_cb_info(env, info, &argc, argv.data(), &thisVar, nullptr);
131
132 napi_value undefined;
133 napi_get_undefined(env, &undefined);
134
135 bool isCallable = false;
136 napi_is_callable(env, argv[0], &isCallable);
137 if (!isCallable) {
138 return undefined;
139 }
140
141 int32_t timeout = 0;
142 if (argc > 1) {
143 napi_status status = napi_get_value_int32(env, argv[1], &timeout);
144 if (status != napi_ok) {
145 napi_throw_error(env, nullptr, "timeout should be number");
146 return nullptr;
147 }
148 }
149 if (timeout < 0) {
150 napi_throw_error(env, nullptr, "timeout < 0 is unreasonable");
151 return nullptr;
152 }
153
154 napi_ref callbackRef = nullptr;
155 napi_create_reference(env, argv[0], 1, &callbackRef);
156
157 std::vector<napi_ref> callbackArgv;
158 for (size_t argsIdx = 2U; argsIdx < argc; argsIdx++) {
159 napi_ref callbackArg = nullptr;
160 napi_create_reference(env, argv[argsIdx], 1, &callbackArg);
161 callbackArgv.push_back(callbackArg);
162 }
163
164 return RegisterTimer(env, callbackRef, std::move(callbackArgv), timeout, repeat);
165 }
166
ClearTimerImpl(napi_env env,napi_callback_info info)167 napi_value ClearTimerImpl(napi_env env, napi_callback_info info)
168 {
169 size_t argc = 1;
170 std::array<napi_value, 1> argv {};
171 napi_get_cb_info(env, info, &argc, argv.data(), nullptr, nullptr);
172 if (argc < 1) {
173 napi_throw_error(env, nullptr, "The number of params must be one");
174 return nullptr;
175 }
176
177 uint32_t tId;
178 napi_status status = napi_get_value_uint32(env, argv[0], &tId);
179 if (status != napi_ok) {
180 napi_throw_error(env, nullptr, "Function param must be number");
181 return nullptr;
182 }
183
184 auto timerIt = g_timers.find(tId);
185 if (timerIt == g_timers.end()) {
186 // timer already cleared
187 return nullptr;
188 }
189 auto *timerInfo = timerIt->second;
190 g_timers.erase(timerIt);
191 delete timerInfo;
192
193 napi_value undefined;
194 napi_get_undefined(env, &undefined);
195 return undefined;
196 }
197
198 } // namespace ark::ets::interop::js::helper
199