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