1 /*
2  * Copyright (c) 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_rss_session.h"
17 
18 #include <js_runtime_utils.h>
19 
20 #include "js_scene_utils.h"
21 #ifdef RESOURCE_SCHEDULE_SERVICE_ENABLE
22 #include "res_sched_client.h"
23 #include "res_type.h"
24 #endif
25 #include "window_manager_hilog.h"
26 
27 namespace OHOS::Rosen {
28 using namespace AbilityRuntime;
29 using namespace ResourceSchedule;
30 namespace {
31 constexpr HiviewDFX::HiLogLabel LABEL = { LOG_CORE, HILOG_DOMAIN_WINDOW, "JsRssSession" };
32 constexpr size_t ARG_COUNT_ONE = 1;
33 constexpr size_t ARG_COUNT_TWO = 2;
34 constexpr int32_t INDENT = -1;
35 } // namespace
36 
37 struct CallBackContext {
38     napi_env env = nullptr;
39     std::shared_ptr<NativeReference> callbackRef = nullptr;
40     OnRssEventCb eventCb = nullptr;
41     uint32_t eventType = 0;
42     std::unordered_map<std::string, std::string> extraInfo;
43 };
44 
45 #ifdef RESOURCE_SCHEDULE_SERVICE_ENABLE
RssEventListener(napi_env env,napi_value callbackObj,OnRssEventCb callback)46 RssEventListener::RssEventListener(napi_env env, napi_value callbackObj, OnRssEventCb callback)
47     : napiEnv_(env), eventCb_(std::move(callback))
48 {
49     napi_ref objRef = nullptr;
50     napi_create_reference(napiEnv_, callbackObj, 1, &objRef);
51     callbackRef_.reset(reinterpret_cast<NativeReference*>(objRef));
52     napi_value callbackWorkName = nullptr;
53     napi_create_string_utf8(env, "ThreadSafeFunction in SystemloadListener", NAPI_AUTO_LENGTH, &callbackWorkName);
54     napi_create_threadsafe_function(env, nullptr, nullptr, callbackWorkName, 0, 1, nullptr, nullptr, nullptr,
55         ThreadSafeCallBack, &threadSafeFunction_);
56 }
57 
ThreadSafeCallBack(napi_env ThreadSafeEnv,napi_value js_cb,void * context,void * data)58 void RssEventListener::ThreadSafeCallBack(napi_env ThreadSafeEnv, napi_value js_cb, void* context, void* data)
59 {
60     WLOGFI("start");
61     CallBackContext* callBackContext = reinterpret_cast<CallBackContext*>(data);
62     callBackContext->eventCb(callBackContext->env,
63         callBackContext->callbackRef->GetNapiValue(), callBackContext->eventType,
64         std::move(callBackContext->extraInfo));
65     delete callBackContext;
66 }
67 
OnReceiveEvent(uint32_t eventType,uint32_t eventValue,std::unordered_map<std::string,std::string> extraInfo)68 void RssEventListener::OnReceiveEvent(uint32_t eventType, uint32_t eventValue,
69     std::unordered_map<std::string, std::string> extraInfo)
70 {
71     CallBackContext* callBackContext = new CallBackContext();
72     callBackContext->env = napiEnv_;
73     callBackContext->callbackRef = callbackRef_;
74     callBackContext->eventType = eventType;
75     callBackContext->extraInfo = std::move(extraInfo);
76     callBackContext->eventCb = eventCb_;
77     napi_acquire_threadsafe_function(threadSafeFunction_);
78     napi_call_threadsafe_function(threadSafeFunction_, callBackContext, napi_tsfn_blocking);
79 }
80 
GetInstance()81 RssSession& RssSession::GetInstance()
82 {
83     static RssSession session;
84     return session;
85 }
86 
RegisterRssData(napi_env env,napi_callback_info info)87 napi_value RssSession::RegisterRssData(napi_env env, napi_callback_info info)
88 {
89     return GetInstance().RegisterRssDataCallback(env, info);
90 }
91 
UnregisterRssData(napi_env env,napi_callback_info info)92 napi_value RssSession::UnregisterRssData(napi_env env, napi_callback_info info)
93 {
94     return GetInstance().UnRegisterRssDataCallback(env, info);
95 }
96 
97 template<typename T>
SetMapValue(napi_env env,const std::string & key,const T & value,napi_value & object)98 void SetMapValue(napi_env env, const std::string& key, const T& value, napi_value& object)
99 {
100     napi_value keyInfo = nullptr;
101     napi_create_string_utf8(env, key.c_str(), NAPI_AUTO_LENGTH, &keyInfo);
102     napi_value valueInfo = nullptr;
103     if constexpr (std::is_same_v<T, bool>) {
104         napi_get_boolean(env, value, &valueInfo);
105     } else if constexpr (std::is_same_v<T, std::string>) {
106         napi_create_string_utf8(env, value.c_str(), NAPI_AUTO_LENGTH, &valueInfo);
107     }
108     napi_set_property(env, object, keyInfo, valueInfo);
109 }
110 
DealRssReply(napi_env env,const nlohmann::json & payload,const nlohmann::json & reply)111 napi_value RssSession::DealRssReply(napi_env env, const nlohmann::json& payload, const nlohmann::json& reply)
112 {
113     WLOGFI("[NAPI]");
114     napi_value objValue = nullptr;
115     napi_create_object(env, &objValue);
116     if (objValue == nullptr) {
117         WLOGFE("[NAPI]Object is null!");
118         return NapiGetUndefined(env);
119     }
120 
121     if (!reply.contains("result") || !reply["result"].is_string() || reply.at("result") == "") {
122         WLOGFE("[NAPI]Reply not find result key!");
123         return NapiGetUndefined(env);
124     }
125     std::string result = reply["result"].get<std::string>();
126     bool resultVal = result == std::to_string(ResType::HeavyLoadMutexAddReasons::HeavyLoadMutexStatusAddFailByMutex);
127     SetMapValue(env, "result", resultVal, objValue);
128 
129     std::string detail = "{}";
130     if (!reply.contains("mutex") || !reply["mutex"].is_string() || reply.at("mutex") == "") {
131         SetMapValue(env, "details", detail, objValue);
132     } else {
133         ParseMutex(reply["mutex"].get<std::string>(), payload, detail);
134         SetMapValue(env, "details", detail, objValue);
135     }
136     return objValue;
137 }
138 
ParseMutex(const std::string & mutexStr,const nlohmann::json & payload,std::string & detailStr)139 void RssSession::ParseMutex(const std::string& mutexStr, const nlohmann::json& payload, std::string& detailStr)
140 {
141     nlohmann::json root = nlohmann::json::parse(mutexStr, nullptr, false);
142     if (root.is_discarded()) {
143         WLOGFE("[NAPI] Parse json data failed!");
144         return;
145     }
146     if (!root.is_array()) {
147         WLOGFE("[NAPI]Parse json data failed!");
148         return;
149     }
150 
151     std::string bundleName;
152     if (payload.contains("bundleName") && payload["bundleName"].is_string()) {
153         bundleName = payload["bundleName"].get<std::string>();
154     }
155 
156     nlohmann::json detail;
157     detail["appInfo"]["domain"] = "";
158     detail["appInfo"]["bundleName"] = std::move(bundleName);
159     detail["reason"] = nlohmann::json::array();
160     for (auto& item : root) {
161         nlohmann::json tmp;
162         if (item.contains("domain") && item["domain"].is_number()) {
163             tmp["domain"] = std::to_string(item["domain"].get<int32_t>());
164         } else {
165             tmp["domain"] = "";
166         }
167 
168         if (item.contains("bundlename") && item["bundlename"].is_string()) {
169             tmp["bundleName"] = item["bundlename"].get<std::string>();
170         } else {
171             tmp["bundleName"] = "";
172         }
173         detail["reason"].push_back(std::move(tmp));
174     }
175     detailStr = detail.dump(INDENT, ' ', false, nlohmann::json::error_handler_t::replace);
176 }
177 
ParseCallbackMutex(const std::string & mutexStr,std::string & bundleName)178 void RssSession::ParseCallbackMutex(const std::string& mutexStr, std::string& bundleName)
179 {
180     nlohmann::json root = nlohmann::json::parse(mutexStr, nullptr, false);
181     if (root.is_discarded()) {
182         WLOGFE("[NAPI] Parse json data failed!");
183         return;
184     }
185     if (!root.is_array()) {
186         WLOGFE("[NAPI]Parse json data failed!");
187         return;
188     }
189 
190     for (auto& item : root) {
191         if (item.contains("bundlename") && item["bundlename"].is_string()) {
192             bundleName = item["bundlename"].get<std::string>();
193             break;
194         }
195     }
196 }
197 
OnReceiveEvent(napi_env env,napi_value callbackObj,uint32_t eventType,const std::unordered_map<std::string,std::string> & extraInfo)198 void RssSession::OnReceiveEvent(napi_env env, napi_value callbackObj, uint32_t eventType,
199     const std::unordered_map<std::string, std::string>& extraInfo)
200 {
201     WLOGFI("asyncCallback.");
202     if (jsCallBackMap_.find(eventType) == jsCallBackMap_.end()) {
203         WLOGFE("cb type has not register yet.");
204         return;
205     }
206     bool isEqual = false;
207     auto& callbackList = jsCallBackMap_[eventType];
208     auto iter = callbackList.begin();
209     for (; iter != callbackList.end(); iter++) {
210         NAPI_CALL_RETURN_VOID(env, napi_strict_equals(env, callbackObj, iter->first->GetNapiValue(), &isEqual));
211         if (isEqual) {
212             break;
213         }
214     }
215     if (!isEqual) {
216         WLOGFE("level callback not found in registered array.");
217         return;
218     }
219     std::unique_ptr<RssSessionCbInfo> cbInfo = std::make_unique<RssSessionCbInfo>(env);
220     cbInfo->extraInfo_ = extraInfo;
221     napi_value resourceName = nullptr;
222     NAPI_CALL_RETURN_VOID(env,
223         napi_create_string_latin1(env, "OnReceiveEvent", NAPI_AUTO_LENGTH, &resourceName));
224     NAPI_CALL_RETURN_VOID(env,
225         napi_create_reference(env, iter->first->GetNapiValue(), 1, &cbInfo->callback_));
226 
227     NAPI_CALL_RETURN_VOID(env, napi_create_async_work(env, nullptr, resourceName,
228         [](napi_env env, void* data) {},
229         CompleteCb,
230         static_cast<void*>(cbInfo.get()),
231         &cbInfo->asyncWork_));
232     NAPI_CALL_RETURN_VOID(env, napi_queue_async_work(env, cbInfo->asyncWork_));
233     cbInfo.release();
234     WLOGFI("asyncCallback end");
235 }
236 
RegisterRssDataCallback(napi_env env,napi_callback_info info)237 napi_value RssSession::RegisterRssDataCallback(napi_env env, napi_callback_info info)
238 {
239     WLOGFD("start");
240     uint32_t eventType;
241     napi_value jsCallback = nullptr;
242     if (!CheckCallbackParam(env, info, eventType, &jsCallback)) {
243         WLOGFE("Register RssData Callback parameter error.");
244         return NapiGetUndefined(env);
245     }
246     napi_ref objRef = nullptr;
247     napi_create_reference(env, jsCallback, 1, &objRef);
248     std::unique_ptr<NativeReference> callbackRef;
249     callbackRef.reset(reinterpret_cast<NativeReference*>(objRef));
250     if (jsCallBackMap_.find(eventType) == jsCallBackMap_.end()) {
251         jsCallBackMap_[eventType] = std::list<CallBackPair>();
252     }
253     bool isEqual = false;
254     auto& callbackList = jsCallBackMap_[eventType];
255     for (auto iter = callbackList.begin(); iter != callbackList.end(); iter++) {
256         napi_strict_equals(env, jsCallback, iter->first->GetNapiValue(), &isEqual);
257         if (isEqual) {
258             WLOGFW("Register a exist callback type.");
259             return NapiGetUndefined(env);
260         }
261     }
262     auto rssDataCb = [](napi_env env, napi_value callbackObj, uint32_t eventType,
263         std::unordered_map<std::string, std::string>&& extraInfo) {
264         RssSession::GetInstance().OnReceiveEvent(env, callbackObj, eventType, extraInfo);
265     };
266     sptr<RssEventListener> eventListener =
267         sptr<RssEventListener>::MakeSptr(env, jsCallback, rssDataCb);
268     ResSchedClient::GetInstance().RegisterEventListener(eventListener, eventType);
269     callbackList.emplace_back(std::move(callbackRef), eventListener);
270     return NapiGetUndefined(env);
271 }
272 
UnRegisterRssDataCallback(napi_env env,napi_callback_info info)273 napi_value RssSession::UnRegisterRssDataCallback(napi_env env, napi_callback_info info)
274 {
275     WLOGFD("start");
276     uint32_t eventType;
277     napi_value jsCallback = nullptr;
278     if (!CheckCallbackParam(env, info, eventType, &jsCallback)) {
279         WLOGFE("UnRegister RssData Callback parameter error.");
280         return NapiGetUndefined(env);
281     }
282     if (jsCallBackMap_.find(eventType) == jsCallBackMap_.end()) {
283         WLOGFE("unRegister eventType has not registered");
284         return NapiGetUndefined(env);
285     }
286     auto& callbackList = jsCallBackMap_[eventType];
287     for (auto iter = callbackList.begin(); iter != callbackList.end(); iter++) {
288         bool isEqual = false;
289         napi_strict_equals(env, jsCallback, iter->first->GetNapiValue(), &isEqual);
290         if (isEqual) {
291             ResSchedClient::GetInstance().UnRegisterEventListener(iter->second, eventType);
292             callbackList.erase(iter);
293             break;
294         }
295     }
296     return NapiGetUndefined(env);
297 }
298 
CompleteCb(napi_env env,napi_status status,void * data)299 void RssSession::CompleteCb(napi_env env, napi_status status, void* data)
300 {
301     WLOGFI("start");
302     auto* info = static_cast<RssSessionCbInfo*>(data);
303     if (info == nullptr) {
304         WLOGFW("Complete cb info is nullptr.");
305         return;
306     }
307     std::unique_ptr<RssSessionCbInfo> cbInfo(info);
308     napi_value undefined = nullptr;
309     NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
310     napi_value callback = nullptr;
311     NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, cbInfo->callback_, &callback));
312     napi_value result = nullptr;
313     napi_create_object(env, &result);
314     SetMapValue(env, "appInfo", cbInfo->extraInfo_["selfBundleName"], result);
315     std::string reason;
316     ParseCallbackMutex(cbInfo->extraInfo_["mutex"], reason);
317     SetMapValue(env, "reason", reason, result);
318 
319     // call js callback
320     napi_value callResult = nullptr;
321     NAPI_CALL_RETURN_VOID(env, napi_call_function(env, undefined, callback, 1, &result, &callResult));
322     // delete resources
323     NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, cbInfo->asyncWork_));
324     cbInfo->asyncWork_ = nullptr;
325     if (cbInfo->callback_ != nullptr) {
326         NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, cbInfo->callback_));
327         cbInfo->callback_ = nullptr;
328     }
329     WLOGFI("end");
330 }
331 
CheckCallbackParam(napi_env env,napi_callback_info info,uint32_t & eventType,napi_value * jsCallback)332 bool RssSession::CheckCallbackParam(napi_env env, napi_callback_info info,
333                                     uint32_t& eventType, napi_value* jsCallback)
334 {
335     if (jsCallback == nullptr) {
336         WLOGFE("Input callback is nullptr.");
337         return false;
338     }
339     size_t argc = ARG_COUNT_TWO;
340     napi_value argv[ARG_COUNT_TWO] = { 0 };
341     NAPI_CALL_BASE(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr), false);
342     if (argc != ARG_COUNT_TWO) {
343         WLOGFE("Parameter error. The type of \"number of parameters\" must be 2");
344         return false;
345     }
346     if (!ConvertFromJsValue(env, argv[0], eventType)) {
347         WLOGFE("Parameter error. The type of \"type\" must be string");
348         return false;
349     }
350     *jsCallback = argv[ARG_COUNT_ONE];
351     if (*jsCallback == nullptr) {
352         WLOGFE("listenerObj is nullptr");
353         return false;
354     }
355     bool isCallable = false;
356     napi_is_callable(env, *jsCallback, &isCallable);
357     if (!isCallable) {
358         WLOGFE("Parameter error. The type of \"callback\" must be Callback");
359         return false;
360     }
361     return true;
362 }
363 #endif
364 } // OHOS