1 /*
2 * Copyright (c) 2022 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 "native_engine/native_engine.h"
19 #include "utils/log.h"
20
21 namespace OHOS::JsSysModule {
22 using namespace Commonlibrary::Concurrent::Common;
23
24 uint32_t Timer::timeCallbackId = 0;
25 std::map<uint32_t, TimerCallbackInfo*> Timer::timerTable;
26 std::mutex Timer::timeLock;
27
~TimerCallbackInfo()28 TimerCallbackInfo::~TimerCallbackInfo()
29 {
30 Helper::NapiHelper::DeleteReference(env_, callback_);
31 for (size_t idx = 0; idx < argc_; idx++) {
32 Helper::NapiHelper::DeleteReference(env_, argv_[idx]);
33 }
34 Helper::CloseHelp::DeletePointer(argv_, true);
35
36 uv_timer_stop(timeReq_);
37 uv_close(reinterpret_cast<uv_handle_t*>(timeReq_), [](uv_handle_t* handle) {
38 if (handle != nullptr) {
39 delete (uv_timer_t*)handle;
40 handle = nullptr;
41 }
42 });
43 }
44
RegisterTime(napi_env env)45 bool Timer::RegisterTime(napi_env env)
46 {
47 if (env == nullptr) {
48 return false;
49 }
50 napi_property_descriptor properties[] = {
51 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("setTimeout", SetTimeout),
52 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("setInterval", SetInterval),
53 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("clearTimeout", ClearTimer),
54 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("clearInterval", ClearTimer)
55 };
56 napi_value globalObj = Helper::NapiHelper::GetGlobalObject(env);
57 napi_status status = napi_define_properties(env, globalObj, sizeof(properties) / sizeof(properties[0]), properties);
58 return status == napi_ok;
59 }
60
SetTimeout(napi_env env,napi_callback_info cbinfo)61 napi_value Timer::SetTimeout(napi_env env, napi_callback_info cbinfo)
62 {
63 return Timer::SetTimeoutInner(env, cbinfo, false);
64 }
65
SetInterval(napi_env env,napi_callback_info cbinfo)66 napi_value Timer::SetInterval(napi_env env, napi_callback_info cbinfo)
67 {
68 return Timer::SetTimeoutInner(env, cbinfo, true);
69 }
70
ClearTimer(napi_env env,napi_callback_info cbinfo)71 napi_value Timer::ClearTimer(napi_env env, napi_callback_info cbinfo)
72 {
73 // 1. check args
74 size_t argc = 1;
75 napi_value argv[1];
76 napi_get_cb_info(env, cbinfo, &argc, argv, nullptr, nullptr);
77 if (argc < 1) {
78 HILOG_ERROR("the number of params must be one");
79 return nullptr;
80 }
81
82 uint32_t tId;
83 napi_status status = napi_get_value_uint32(env, argv[0], &tId);
84 if (status != napi_ok) {
85 HILOG_ERROR("first param should be number");
86 return nullptr;
87 }
88
89 {
90 std::lock_guard<std::mutex> lock(timeLock);
91 auto iter = timerTable.find(tId);
92 if (iter == timerTable.end()) {
93 HILOG_ERROR("timerId is inexistent");
94 return nullptr;
95 }
96 TimerCallbackInfo* callbackInfo = iter->second;
97 timerTable.erase(tId);
98 Helper::CloseHelp::DeletePointer(callbackInfo, false);
99 }
100 return Helper::NapiHelper::GetUndefinedValue(env);
101 }
102
TimerCallback(uv_timer_t * handle)103 void Timer::TimerCallback(uv_timer_t* handle)
104 {
105 TimerCallbackInfo* callbackInfo = static_cast<TimerCallbackInfo*>(handle->data);
106 if (callbackInfo == nullptr) {
107 return;
108 }
109 // Save the following parameters to ensure that they can still obtained if callback clears the callbackInfo.
110 bool repeat = callbackInfo->repeat_;
111 uint32_t tId = callbackInfo->tId_;
112 napi_env env = callbackInfo->env_;
113
114 napi_handle_scope scope = nullptr;
115 napi_open_handle_scope(env, &scope);
116 if (scope == nullptr) {
117 return;
118 }
119 napi_value callback = Helper::NapiHelper::GetReferenceValue(env, callbackInfo->callback_);
120 napi_value undefinedValue = Helper::NapiHelper::GetUndefinedValue(env);
121 napi_value callbackResult = nullptr;
122 napi_value* callbackArgv = new napi_value[callbackInfo->argc_];
123 for (size_t idx = 0; idx < callbackInfo->argc_; idx++) {
124 callbackArgv[idx] = Helper::NapiHelper::GetReferenceValue(env, callbackInfo->argv_[idx]);
125 }
126
127 napi_call_function(env, undefinedValue, callback,
128 callbackInfo->argc_, callbackArgv, &callbackResult);
129 Helper::CloseHelp::DeletePointer(callbackArgv, true);
130 bool isPendingException = false;
131 napi_is_exception_pending(env, &isPendingException);
132 NativeEngine* engine = reinterpret_cast<NativeEngine*>(env);
133 if (isPendingException && engine->IsMainThread()) {
134 HILOG_ERROR("Pending exception in TimerCallback. Triggering HandleUncaughtException");
135 engine->HandleUncaughtException();
136 napi_close_handle_scope(env, scope);
137 return;
138 }
139 if (callbackResult == nullptr) {
140 HILOG_ERROR("call timerCallback error");
141 napi_close_handle_scope(env, scope);
142 Helper::CloseHelp::DeletePointer(callbackInfo, false);
143 return;
144 }
145
146 // callback maybe contain ClearTimer, so we need check to avoid use-after-free and double-free of callbackInfo
147 std::lock_guard<std::mutex> lock(timeLock);
148 if (timerTable.find(tId) == timerTable.end()) {
149 HILOG_WARN("not found timerCallbackInfo");
150 napi_close_handle_scope(env, scope);
151 return;
152 }
153 if (!repeat) {
154 timerTable.erase(tId);
155 napi_close_handle_scope(env, scope);
156 Helper::CloseHelp::DeletePointer(callbackInfo, false);
157 } else {
158 napi_close_handle_scope(env, scope);
159 uv_timer_again(handle);
160 }
161 }
162
SetTimeoutInner(napi_env env,napi_callback_info cbinfo,bool repeat)163 napi_value Timer::SetTimeoutInner(napi_env env, napi_callback_info cbinfo, bool repeat)
164 {
165 // 1. check args
166 size_t argc = Helper::NapiHelper::GetCallbackInfoArgc(env, cbinfo);
167 if (argc < 1) {
168 napi_throw_error(env, nullptr, "StartTimeoutOrInterval, callback info is nullptr.");
169 return nullptr;
170 }
171 napi_value* argv = new napi_value[argc];
172 Helper::ObjectScope<napi_value> scope(argv, true);
173 napi_value thisVar = nullptr;
174 napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, nullptr);
175 if (!Helper::NapiHelper::IsCallable(env, argv[0])) {
176 HILOG_ERROR("Set callback timer failed with invalid parameter.");
177 return Helper::NapiHelper::GetUndefinedValue(env);
178 }
179 int32_t timeout = 0;
180 if (argc > 1) {
181 napi_status status = napi_get_value_int32(env, argv[1], &timeout);
182 if (status != napi_ok) {
183 HILOG_WARN("timeout should be number");
184 timeout = 0;
185 }
186 }
187 if (timeout < 0) {
188 HILOG_WARN("timeout < 0 is unreasonable");
189 }
190 // 2. get callback args
191 size_t callbackArgc = argc >= 2 ? argc - 2 : 0; // 2 include callback and timeout
192 napi_ref* callbackArgv = nullptr;
193 if (callbackArgc > 0) {
194 callbackArgv = new napi_ref[callbackArgc];
195 for (size_t idx = 0; idx < callbackArgc; idx++) {
196 callbackArgv[idx] =
197 Helper::NapiHelper::CreateReference(env, argv[idx + 2], 1); // 2 include callback and timeout
198 }
199 }
200
201 // 3. generate time callback id
202 // 4. generate time callback info
203 // 5. push callback info into timerTable
204 uint32_t tId = 0;
205 TimerCallbackInfo* callbackInfo = nullptr;
206 {
207 std::lock_guard<std::mutex> lock(timeLock);
208 tId = timeCallbackId++;
209 napi_ref callbackRef = Helper::NapiHelper::CreateReference(env, argv[0], 1);
210 callbackInfo = new TimerCallbackInfo(env, tId, timeout, callbackRef, repeat, callbackArgc, callbackArgv);
211 if (timerTable.find(tId) != timerTable.end()) {
212 HILOG_ERROR("timerTable occurs error");
213 } else {
214 timerTable[tId] = callbackInfo;
215 }
216 }
217
218 // 6. start timer
219 uv_loop_t* loop = Helper::NapiHelper::GetLibUV(env);
220 NativeEngine* engine = reinterpret_cast<NativeEngine*>(env);
221 if (engine->IsMainThread()) {
222 uv_update_time(loop);
223 }
224 uv_timer_start(callbackInfo->timeReq_, TimerCallback, timeout >= 0 ? timeout : 1, timeout > 0 ? timeout : 1);
225 if (engine->IsMainThread()) {
226 uv_work_t *work = new uv_work_t;
227 uv_queue_work(loop, work, [](uv_work_t *){ }, [](uv_work_t *work, int32_t) {delete work; });
228 }
229 return Helper::NapiHelper::CreateUint32(env, tId);
230 }
231
ClearEnvironmentTimer(napi_env env)232 void Timer::ClearEnvironmentTimer(napi_env env)
233 {
234 std::lock_guard<std::mutex> lock(timeLock);
235 auto iter = timerTable.begin();
236 while (iter != timerTable.end()) {
237 TimerCallbackInfo* callbackInfo = iter->second;
238 if (callbackInfo->env_ == env) {
239 iter = timerTable.erase(iter);
240 Helper::CloseHelp::DeletePointer(callbackInfo, false);
241 } else {
242 iter++;
243 }
244 }
245 }
246
HasTimer(napi_env env)247 bool Timer::HasTimer(napi_env env)
248 {
249 std::lock_guard<std::mutex> lock(timeLock);
250 auto iter = std::find_if(timerTable.begin(), timerTable.end(), [env](const auto &item) {
251 return item.second->env_ == env;
252 });
253 return iter != timerTable.end();
254 }
255 } // namespace Commonlibrary::JsSysModule
256