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