1 /*
2  * Copyright (c) 2022-2024 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 "js_timer.h"
17 
18 #include <atomic>
19 #include <memory>
20 #include <mutex>
21 #include <string>
22 #include <vector>
23 #include <unordered_map>
24 
25 #include "hilog_tag_wrapper.h"
26 #include "js_runtime.h"
27 #include "js_runtime_utils.h"
28 
29 namespace OHOS {
30 namespace AbilityRuntime {
31 namespace {
32 class JsTimer;
33 
34 std::atomic<uint32_t> g_callbackId(1);
35 std::mutex g_mutex;
36 std::unordered_map<uint32_t, std::shared_ptr<JsTimer>> g_timerTable;
37 
38 class JsTimer final {
39 public:
JsTimer(napi_env env,const std::shared_ptr<NativeReference> & jsFunction,uint32_t id)40     JsTimer(napi_env env, const std::shared_ptr<NativeReference> &jsFunction, uint32_t id)
41         : env_(env), jsFunction_(jsFunction), id_(id)
42     {
43         uv_loop_s* loop = nullptr;
44         napi_get_uv_event_loop(env_, &loop);
45         if (loop == nullptr) {
46             TAG_LOGE(AAFwkTag::ABILITY_SIM, "null loop");
47             return;
48         }
49         uv_timer_init(loop, &timerReq_);
50         timerReq_.data = this;
51     }
52 
~JsTimer()53     ~JsTimer()
54     {
55         uv_timer_stop(&timerReq_);
56     }
57 
Start(int64_t timeout,int64_t repeat)58     void Start(int64_t timeout, int64_t repeat)
59     {
60         uv_timer_start(&timerReq_, [](uv_timer_t *timerReq) {
61             auto me = static_cast<JsTimer*>(timerReq->data);
62             me->OnTimeout();
63         }, timeout, repeat);
64     }
65 
OnTimeout()66     void OnTimeout()
67     {
68         std::vector<napi_value> args;
69         args.reserve(jsArgs_.size());
70         for (auto arg : jsArgs_) {
71             args.emplace_back(arg->GetNapiValue());
72         }
73         napi_value res = nullptr;
74         napi_call_function(env_, CreateJsUndefined(env_),
75             jsFunction_->GetNapiValue(), args.size(), args.data(), &res);
76 
77         if (uv_timer_get_repeat(&timerReq_) == 0) {
78             std::lock_guard<std::mutex> lock(g_mutex);
79             g_timerTable.erase(id_);
80         }
81     }
82 
PushArgs(const std::shared_ptr<NativeReference> & ref)83     void PushArgs(const std::shared_ptr<NativeReference> &ref)
84     {
85         jsArgs_.emplace_back(ref);
86     }
87 
88 private:
89     napi_env env_;
90     std::shared_ptr<NativeReference> jsFunction_;
91     std::vector<std::shared_ptr<NativeReference>> jsArgs_;
92     uv_timer_t timerReq_;
93     uint32_t id_ = 0;
94 };
95 
StartTimeoutOrInterval(napi_env env,napi_callback_info info,bool isInterval)96 napi_value StartTimeoutOrInterval(napi_env env, napi_callback_info info, bool isInterval)
97 {
98     if (env == nullptr || info == nullptr) {
99         TAG_LOGE(AAFwkTag::ABILITY_SIM, "null env/callback info");
100         return nullptr;
101     }
102     size_t argc = ARGC_MAX_COUNT;
103     napi_value argv[ARGC_MAX_COUNT] = {nullptr};
104     napi_value thisVar = nullptr;
105     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
106 
107     // parameter check, must have at least 2 params
108     if (argc < 2 ||!CheckTypeForNapiValue(env, argv[0], napi_function)
109         || !CheckTypeForNapiValue(env, argv[1], napi_number)) {
110         TAG_LOGE(AAFwkTag::ABILITY_SIM, "invalid parameter");
111         return CreateJsUndefined(env);
112     }
113 
114     // parse parameter
115     napi_ref ref = nullptr;
116     napi_create_reference(env, argv[0], 1, &ref);
117     std::shared_ptr<NativeReference> jsFunction(reinterpret_cast<NativeReference*>(ref));
118     int64_t delayTime = 0;
119     napi_get_value_int64(env, argv[1], &delayTime);
120     uint32_t callbackId = g_callbackId.fetch_add(1, std::memory_order_relaxed);
121 
122     auto task = std::make_shared<JsTimer>(env, jsFunction, callbackId);
123     for (size_t index = 2; index < argc; ++index) {
124         napi_ref taskRef = nullptr;
125         napi_create_reference(env, argv[index], 1, &taskRef);
126         task->PushArgs(std::shared_ptr<NativeReference>(reinterpret_cast<NativeReference*>(taskRef)));
127     }
128 
129     // if setInterval is called, interval must not be zero for repeat, so set to 1ms
130     int64_t interval = 0;
131     if (isInterval) {
132         interval = delayTime > 0 ? delayTime : 1;
133     }
134     task->Start(delayTime, interval);
135 
136     {
137         std::lock_guard<std::mutex> lock(g_mutex);
138         g_timerTable.emplace(callbackId, task);
139     }
140 
141     return CreateJsValue(env, callbackId);
142 }
143 
StartTimeout(napi_env env,napi_callback_info info)144 napi_value StartTimeout(napi_env env, napi_callback_info info)
145 {
146     return StartTimeoutOrInterval(env, info, false);
147 }
148 
StartInterval(napi_env env,napi_callback_info info)149 napi_value StartInterval(napi_env env, napi_callback_info info)
150 {
151     return StartTimeoutOrInterval(env, info, true);
152 }
153 
StopTimeoutOrInterval(napi_env env,napi_callback_info info)154 napi_value StopTimeoutOrInterval(napi_env env, napi_callback_info info)
155 {
156     if (env == nullptr || info == nullptr) {
157         TAG_LOGE(AAFwkTag::ABILITY_SIM, "null env/callback info");
158         return nullptr;
159     }
160     size_t argc = ARGC_MAX_COUNT;
161     napi_value argv[ARGC_MAX_COUNT] = {nullptr};
162     napi_value thisVar = nullptr;
163     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
164 
165     // parameter check, must have at least 1 param
166     if (argc < 1 || !CheckTypeForNapiValue(env, argv[0], napi_number)) {
167         TAG_LOGE(AAFwkTag::ABILITY_SIM, "invalid parameter");
168         return CreateJsUndefined(env);
169     }
170     uint32_t callbackId = 0;
171     napi_get_value_uint32(env, argv[0], &callbackId);
172     {
173         std::lock_guard<std::mutex> lock(g_mutex);
174         g_timerTable.erase(callbackId);
175     }
176     return CreateJsUndefined(env);
177 }
178 }
179 
InitTimer(napi_env env,napi_value globalObject)180 void InitTimer(napi_env env, napi_value globalObject)
181 {
182     const char *moduleName = "AsJsTimer";
183     BindNativeFunction(env, globalObject, "setTimeout", moduleName, StartTimeout);
184     BindNativeFunction(env, globalObject, "setInterval", moduleName, StartInterval);
185     BindNativeFunction(env, globalObject, "clearTimeout", moduleName, StopTimeoutOrInterval);
186     BindNativeFunction(env, globalObject, "clearInterval", moduleName, StopTimeoutOrInterval);
187 }
188 } // namespace AbilityRuntime
189 } // namespace OHOS