1 /*
2  * Copyright (c) 2023 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 #include <cstring>
16 #include <memory>
17 
18 #include "interfaces/napi/kits/utils/napi_utils.h"
19 #include "napi/native_api.h"
20 #include "napi/native_node_api.h"
21 #include "interfaces/napi/kits/display_sync/js_display_sync.h"
22 
23 #include "base/log/log.h"
24 #include "base/memory/ace_type.h"
25 #include "base/memory/referenced.h"
26 #include "bridge/common/utils/utils.h"
27 
28 namespace OHOS::Ace::Napi {
29 constexpr size_t STR_MAX_BUFFER_SIZE = 1024;
30 constexpr size_t CALLBACK_OJECT_NUM = 1;
31 constexpr size_t ARGC_NUM_SIZE = 2;
32 
NapiGetUndefined(napi_env env)33 napi_value NapiGetUndefined(napi_env env)
34 {
35     napi_value result = nullptr;
36     napi_get_undefined(env, &result);
37     return result;
38 }
39 
NapiPrintErrorInfo(napi_env env)40 void NapiPrintErrorInfo(napi_env env)
41 {
42     const napi_extended_error_info *error_info;
43     napi_get_last_error_info(env, &error_info);
44     LOGE("JsDisplaySync ErrorInfo: %{public}s", error_info->error_message);
45     return;
46 }
47 
ParseJsValue(napi_env env,napi_value jsObject,const std::string & name,int32_t & data)48 bool ParseJsValue(napi_env env, napi_value jsObject, const std::string& name, int32_t& data)
49 {
50     napi_value value = nullptr;
51     napi_get_named_property(env, jsObject, name.c_str(), &value);
52     napi_valuetype type = napi_undefined;
53     napi_typeof(env, value, &type);
54     if (type == napi_number) {
55         napi_get_value_int32(env, value, &data);
56         return true;
57     } else {
58         return false;
59     }
60     return true;
61 }
62 
ParseArgs(napi_env & env,napi_callback_info & info,napi_value & thisVar,napi_value & cb,CallbackType & callbackType)63 static size_t ParseArgs(
64     napi_env& env, napi_callback_info& info, napi_value& thisVar, napi_value& cb, CallbackType& callbackType)
65 {
66     size_t argc = ARGC_NUM_SIZE;
67     napi_value argv[ARGC_NUM_SIZE] = { 0 };
68     napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
69     NAPI_ASSERT_BASE(env, argc > 0, "too few parameter", 0);
70 
71     napi_valuetype napiType;
72     NAPI_CALL_BASE(env, napi_typeof(env, argv[0], &napiType), 0);
73     NAPI_ASSERT_BASE(env, napiType == napi_string, "parameter 1 should be string", 0);
74     char type[STR_MAX_BUFFER_SIZE] = { 0 };
75     size_t len = 0;
76     napi_get_value_string_utf8(env, argv[0], type, STR_MAX_BUFFER_SIZE, &len);
77     NAPI_ASSERT_BASE(env, len < STR_MAX_BUFFER_SIZE, "condition string too long", 0);
78     NAPI_ASSERT_BASE(
79         env, (strcmp("frame", type) == 0), "type mismatch('frame')", 0);
80     if (strcmp("frame", type) == 0) {
81         callbackType = CallbackType::ONFRAME;
82     } else {
83         callbackType = CallbackType::UNKNOW;
84     }
85 
86     if (argc <= 1) {
87         return argc;
88     }
89 
90     NAPI_CALL_BASE(env, napi_typeof(env, argv[1], &napiType), 0);
91     NAPI_ASSERT_BASE(env, napiType == napi_function, "type mismatch for parameter 2", 0);
92     cb = argv[1];
93     return argc;
94 }
95 
GetDisplaySync(napi_env env,napi_callback_info info)96 DisplaySync* GetDisplaySync(napi_env env, napi_callback_info info)
97 {
98     DisplaySync* displaySync = nullptr;
99     napi_value thisVar = nullptr;
100     napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
101     napi_unwrap(env, thisVar, reinterpret_cast<void**>(&displaySync));
102     return displaySync;
103 }
104 
CreateTimeInfoJsObject(const napi_env env,RefPtr<DisplaySyncData> displaySyncData,napi_value & intervalInfo)105 void CreateTimeInfoJsObject(const napi_env env, RefPtr<DisplaySyncData> displaySyncData,
106                             napi_value& intervalInfo)
107 {
108     napi_status status = napi_create_object(env, &intervalInfo);
109     if (status != napi_ok) {
110         NapiPrintErrorInfo(env);
111         return;
112     }
113 
114     napi_value timestamp;
115     napi_value targetTimestamp;
116     napi_create_int64(env, displaySyncData->timestamp_, &timestamp);
117     napi_create_int64(env, displaySyncData->targetTimestamp_, &targetTimestamp);
118     auto resultTimestamp = napi_set_named_property(env, intervalInfo, "timestamp", timestamp);
119     auto resultTargetTimestamp = napi_set_named_property(env, intervalInfo, "targetTimestamp", targetTimestamp);
120     if (resultTimestamp != napi_ok || resultTargetTimestamp != napi_ok) {
121         NapiPrintErrorInfo(env);
122         return;
123     }
124 }
125 
ParseExpectedFrameRateRange(napi_env env,napi_callback_info info,FrameRateRange & frameRateRange)126 napi_value ParseExpectedFrameRateRange(napi_env env, napi_callback_info info, FrameRateRange& frameRateRange)
127 {
128     size_t argc = 1;
129     napi_value argv[1];
130     napi_value thisVar = nullptr;
131     napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr);
132     if (argc != 1) {
133         NapiThrow(env, "The number of parameters is incorrect.", ERROR_CODE_PARAM_INVALID);
134         return NapiGetUndefined(env);
135     }
136 
137     napi_value nativeObj = argv[0];
138     if (nativeObj == nullptr) {
139         NapiThrow(env, "The nativeObj is nullptr.", ERROR_CODE_PARAM_INVALID);
140         return NapiGetUndefined(env);
141     }
142 
143     int32_t minFPS = 0;
144     int32_t maxFPS = 0;
145     int32_t expectedFPS = 0;
146     ParseJsValue(env, nativeObj, "min", minFPS);
147     ParseJsValue(env, nativeObj, "max", maxFPS);
148     ParseJsValue(env, nativeObj, "expected", expectedFPS);
149 
150     frameRateRange.Set(minFPS, maxFPS, expectedFPS);
151     if (!frameRateRange.IsValid()) {
152         NapiThrow(env, "ExpectedFrameRateRange Error", ERROR_CODE_PARAM_INVALID);
153         return NapiGetUndefined(env);
154     }
155     return NapiGetUndefined(env);
156 }
157 
JSSetExpectedFrameRateRange(napi_env env,napi_callback_info info)158 napi_value JSSetExpectedFrameRateRange(napi_env env, napi_callback_info info)
159 {
160     FrameRateRange frameRateRange;
161     ParseExpectedFrameRateRange(env, info, frameRateRange);
162 
163     auto displaySync = GetDisplaySync(env, info);
164     if (!displaySync) {
165         TAG_LOGW(AceLogTag::ACE_DISPLAY_SYNC, "JSSetExpectedFrameRateRange: cannot find displaySync.");
166         return NapiGetUndefined(env);
167     }
168     RefPtr<UIDisplaySync> uiDisplaySync = displaySync->GetUIDisplaySync();
169     if (!uiDisplaySync) {
170         TAG_LOGW(AceLogTag::ACE_DISPLAY_SYNC, "JSSetExpectedFrameRateRange: cannot get uiDisplaySync.");
171         return NapiGetUndefined(env);
172     }
173 
174     uiDisplaySync->SetExpectedFrameRateRange(frameRateRange);
175     TAG_LOGI(AceLogTag::ACE_DISPLAY_SYNC, "Id: %{public}" PRIu64 " SetExpectedFrameRateRange"
176         "{%{public}d, %{public}d, %{public}d}", uiDisplaySync->GetId(), frameRateRange.min_, frameRateRange.max_,
177         frameRateRange.preferred_);
178     return NapiGetUndefined(env);
179 }
180 
JSStart(napi_env env,napi_callback_info info)181 napi_value JSStart(napi_env env, napi_callback_info info)
182 {
183     auto displaySync = GetDisplaySync(env, info);
184     if (!displaySync) {
185         TAG_LOGW(AceLogTag::ACE_DISPLAY_SYNC, "JSStart: cannot find displaySync.");
186         return NapiGetUndefined(env);
187     }
188     RefPtr<UIDisplaySync> uiDisplaySync = displaySync->GetUIDisplaySync();
189     if (!uiDisplaySync) {
190         TAG_LOGW(AceLogTag::ACE_DISPLAY_SYNC, "JSStart: cannot get uiDisplaySync when starting.");
191         return NapiGetUndefined(env);
192     }
193 
194     uiDisplaySync->AddToPipelineOnContainer();
195     TAG_LOGI(AceLogTag::ACE_DISPLAY_SYNC, "Id: %{public}" PRIu64 " Start", uiDisplaySync->GetId());
196     return NapiGetUndefined(env);
197 }
198 
JSStop(napi_env env,napi_callback_info info)199 napi_value JSStop(napi_env env, napi_callback_info info)
200 {
201     auto displaySync = GetDisplaySync(env, info);
202     if (!displaySync) {
203         TAG_LOGW(AceLogTag::ACE_DISPLAY_SYNC, "JSStop: cannot find displaySync.");
204         return NapiGetUndefined(env);
205     }
206     RefPtr<UIDisplaySync> uiDisplaySync = displaySync->GetUIDisplaySync();
207     if (!uiDisplaySync) {
208         TAG_LOGW(AceLogTag::ACE_DISPLAY_SYNC, "JSStop: cannot get uiDisplaySync when stopping.");
209         return NapiGetUndefined(env);
210     }
211 
212     uiDisplaySync->DelFromPipelineOnContainer();
213     TAG_LOGI(AceLogTag::ACE_DISPLAY_SYNC, "Id: %{public}" PRIu64 " Stop", uiDisplaySync->GetId());
214     return NapiGetUndefined(env);
215 }
216 
Initialize(napi_env env,napi_value thisVar)217 void DisplaySync::Initialize(napi_env env, napi_value thisVar)
218 {
219     napi_handle_scope scope = nullptr;
220     napi_open_handle_scope(env, &scope);
221     if (!scope) {
222         return;
223     }
224     napi_create_reference(env, thisVar, 1, &thisVarRef_);
225     napi_close_handle_scope(env, scope);
226 }
227 
NapiSerializer(napi_env & env,napi_value & jsDisplaySync)228 void DisplaySync::NapiSerializer(napi_env& env, napi_value& jsDisplaySync)
229 {
230     napi_status status = napi_create_object(env, &jsDisplaySync);
231     if (status != napi_ok) {
232         NapiPrintErrorInfo(env);
233         return;
234     }
235 
236     napi_wrap(
237         env, jsDisplaySync, this,
238         [](napi_env env, void* data, void* hint) {
239             DisplaySync* displaySync = static_cast<DisplaySync*>(data);
240             if (displaySync) {
241                 displaySync->Destroy(env);
242                 delete displaySync;
243             }
244         },
245         nullptr, nullptr);
246 }
247 
RegisterOnFrameCallback(napi_value cb,napi_ref & onFrameRef,CallbackType callbackType,napi_env env)248 void DisplaySync::RegisterOnFrameCallback(napi_value cb, napi_ref& onFrameRef,
249     CallbackType callbackType, napi_env env)
250 {
251     if (onFrameRef) {
252         return;
253     }
254     napi_create_reference(env, cb, 1, &onFrameRef);
255 
256     GetUIDisplaySync()->RegisterOnFrameWithData([env, onFrameRef] (RefPtr<DisplaySyncData> displaySyncData) {
257         napi_handle_scope innerScope = nullptr;
258         napi_status status = napi_open_handle_scope(env, &innerScope);
259         if (status != napi_ok) {
260             NapiPrintErrorInfo(env);
261             return;
262         }
263 
264         napi_value ret = nullptr;
265         napi_value onframe = nullptr;
266         auto result = napi_get_reference_value(env, onFrameRef, &onframe);
267         if (result != napi_ok || onframe == nullptr) {
268             NapiPrintErrorInfo(env);
269             napi_close_handle_scope(env, innerScope);
270             return;
271         }
272 
273         napi_value intervalInfo = nullptr;
274         CreateTimeInfoJsObject(env, displaySyncData, intervalInfo);
275         napi_value args[CALLBACK_OJECT_NUM] = { intervalInfo };
276         napi_call_function(env, nullptr, onframe, CALLBACK_OJECT_NUM, args, &ret);
277         napi_close_handle_scope(env, innerScope);
278     });
279 }
280 
UnregisterOnFrameCallback(napi_env env,size_t argc,napi_ref & onFrameRef)281 void DisplaySync::UnregisterOnFrameCallback(napi_env env, size_t argc, napi_ref& onFrameRef)
282 {
283     if (argc >= 1) {
284         napi_delete_reference(env, onFrameRef);
285         onFrameRef = nullptr;
286         GetUIDisplaySync()->UnregisterOnFrame();
287     }
288     return;
289 }
290 
Destroy(napi_env env)291 void DisplaySync::Destroy(napi_env env)
292 {
293     if (onFrameRef_ != nullptr) {
294         napi_delete_reference(env, onFrameRef_);
295     }
296 
297     if (thisVarRef_ != nullptr) {
298         napi_delete_reference(env, thisVarRef_);
299     }
300 }
301 
JSOnFrame_On(napi_env env,napi_callback_info info)302 napi_value JSOnFrame_On(napi_env env, napi_callback_info info)
303 {
304     napi_value thisVar = nullptr;
305     napi_value cb = nullptr;
306     CallbackType callbackType = CallbackType::UNKNOW;
307     size_t argc = ParseArgs(env, info, thisVar, cb, callbackType);
308     NAPI_ASSERT(env, (argc == ARGC_NUM_SIZE && thisVar != nullptr && cb != nullptr), "Invalid arguments");
309 
310     DisplaySync* displaySync = GetDisplaySync(env, info);
311     if (!displaySync) {
312         return NapiGetUndefined(env);
313     }
314 
315     if (callbackType == CallbackType::ONFRAME) {
316         displaySync->RegisterOnFrameCallback(cb, displaySync->onFrameRef_, callbackType, env);
317     }
318     return NapiGetUndefined(env);
319 }
320 
JSOnFrame_Off(napi_env env,napi_callback_info info)321 napi_value JSOnFrame_Off(napi_env env, napi_callback_info info)
322 {
323     napi_value thisVar = nullptr;
324     napi_value cb = nullptr;
325     CallbackType callbackType = CallbackType::UNKNOW;
326     size_t argc = ParseArgs(env, info, thisVar, cb, callbackType);
327     DisplaySync* displaySync = GetDisplaySync(env, info);
328     if (!displaySync) {
329         return NapiGetUndefined(env);
330     }
331     if (callbackType == CallbackType::ONFRAME) {
332         displaySync->UnregisterOnFrameCallback(env, argc, displaySync->onFrameRef_);
333     }
334     return NapiGetUndefined(env);
335 }
336 
JSCreate(napi_env env,napi_callback_info info)337 static napi_value JSCreate(napi_env env, napi_callback_info info)
338 {
339     auto uiDisplaySync = AceType::MakeRefPtr<UIDisplaySync>();
340     DisplaySync* displaySync = new DisplaySync(uiDisplaySync);
341 
342     napi_value jsDisplaySync = nullptr;
343     displaySync->NapiSerializer(env, jsDisplaySync);
344     if (!jsDisplaySync) {
345         delete displaySync;
346         return nullptr;
347     }
348 
349     napi_property_descriptor resultFuncs[] = {
350         DECLARE_NAPI_FUNCTION("setExpectedFrameRateRange", JSSetExpectedFrameRateRange),
351         DECLARE_NAPI_FUNCTION("on", JSOnFrame_On),
352         DECLARE_NAPI_FUNCTION("off", JSOnFrame_Off),
353         DECLARE_NAPI_FUNCTION("start", JSStart),
354         DECLARE_NAPI_FUNCTION("stop", JSStop),
355     };
356 
357     TAG_LOGD(AceLogTag::ACE_DISPLAY_SYNC, "Create UIDisplaySync Id: %{public}" PRIu64 "",
358         uiDisplaySync->GetId());
359     NAPI_CALL(env, napi_define_properties(
360         env, jsDisplaySync, sizeof(resultFuncs) / sizeof(resultFuncs[0]), resultFuncs));
361     return jsDisplaySync;
362 }
363 
DisplaySyncExport(napi_env env,napi_value exports)364 static napi_value DisplaySyncExport(napi_env env, napi_value exports)
365 {
366     napi_property_descriptor displaySyncDesc[] = {
367         DECLARE_NAPI_FUNCTION("create", JSCreate),
368     };
369     NAPI_CALL(env, napi_define_properties(
370         env, exports, sizeof(displaySyncDesc) / sizeof(displaySyncDesc[0]), displaySyncDesc));
371     return exports;
372 }
373 
374 static napi_module displaySyncModule = {
375     .nm_version = 1,
376     .nm_flags = 0,
377     .nm_filename = nullptr,
378     .nm_register_func = DisplaySyncExport,
379     .nm_modname = "graphics.displaySync",
380     .nm_priv = ((void*)0),
381     .reserved = { 0 },
382 };
383 
DisplaySyncRegister()384 extern "C" __attribute__((constructor)) void DisplaySyncRegister()
385 {
386     napi_module_register(&displaySyncModule);
387 }
388 } // namespace OHOS::Ace::Napi
389