• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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