• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022-2025 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_ui_appearance.h"
17 
18 #include <string>
19 #include "js_native_api.h"
20 #include "ui_appearance_log.h"
21 
22 namespace OHOS {
23 namespace ArkUi::UiAppearance {
24 namespace {
25 static constexpr size_t ARGC_WITH_ONE = 1;
26 static constexpr size_t ARGC_WITH_TWO = 2;
27 static constexpr size_t MAX_FONT_SCALE = 5;
28 static constexpr size_t MIN_FONT_SCALE = 0;
29 const std::string PERMISSION_ERR_MSG =
30     "An attempt was made to update configuration forbidden by permission: ohos.permission.UPDATE_CONFIGURATION.";
31 const std::string INVALID_ARG_MSG = "The type of 'mode' must be DarkMode.";
32 
ParseErrCode(const int32_t errCode)33 std::string ParseErrCode(const int32_t errCode)
34 {
35     switch (errCode) {
36         case UiAppearanceAbilityErrCode::PERMISSION_ERR:
37             return "Permission denied. ";
38         case UiAppearanceAbilityErrCode::INVALID_ARG:
39             return "Parameter error. ";
40         case UiAppearanceAbilityErrCode::SYS_ERR:
41             return "Internal error. ";
42         default:
43             return "";
44     }
45 }
46 
NapiThrow(napi_env env,const std::string & message,int32_t errCode)47 void NapiThrow(napi_env env, const std::string& message, int32_t errCode)
48 {
49     napi_value code = nullptr;
50     std::string strCode = std::to_string(errCode);
51     napi_create_string_utf8(env, strCode.c_str(), strCode.length(), &code);
52 
53     napi_value msg = nullptr;
54     std::string strMsg = ParseErrCode(errCode) + message;
55     LOGI("napi throw errCode %{public}d, strMsg %{public}s", errCode, strMsg.c_str());
56     napi_create_string_utf8(env, strMsg.c_str(), strMsg.length(), &msg);
57 
58     napi_value error = nullptr;
59     napi_create_error(env, code, msg, &error);
60     napi_throw(env, error);
61 }
62 } // namespace
63 
OnExecute(napi_env env,void * data)64 void JsUiAppearance::OnExecute(napi_env env, void* data)
65 {
66     LOGI("OnExecute begin.");
67     AsyncContext* asyncContext = static_cast<AsyncContext*>(data);
68     if (asyncContext == nullptr) {
69         NapiThrow(env, "asyncContext is null.", UiAppearanceAbilityErrCode::SYS_ERR);
70         return;
71     }
72     auto resCode = UiAppearanceAbilityClient::GetInstance()->SetDarkMode(asyncContext->mode);
73     asyncContext->status = static_cast<UiAppearanceAbilityErrCode>(resCode);
74     if (asyncContext->status == UiAppearanceAbilityErrCode::PERMISSION_ERR) {
75         asyncContext->errMsg = PERMISSION_ERR_MSG;
76     } else if (asyncContext->status == UiAppearanceAbilityErrCode::INVALID_ARG) {
77         asyncContext->errMsg = INVALID_ARG_MSG;
78     } else {
79         asyncContext->errMsg = "";
80     }
81 }
82 
OnSetFontScale(napi_env env,void * data)83 void JsUiAppearance::OnSetFontScale(napi_env env, void* data)
84 {
85     LOGI("OnSetFontScale begin.");
86     AsyncContext* asyncContext = static_cast<AsyncContext*>(data);
87     if (asyncContext == nullptr) {
88         NapiThrow(env, "asyncContext is null.", UiAppearanceAbilityErrCode::SYS_ERR);
89         return;
90     }
91     int32_t resCode = 0;
92     if (asyncContext->jsFontScale <= MIN_FONT_SCALE || asyncContext->jsFontScale > MAX_FONT_SCALE) {
93         resCode = UiAppearanceAbilityErrCode::INVALID_ARG;
94     } else {
95         resCode = UiAppearanceAbilityClient::GetInstance()->SetFontScale(asyncContext->fontScale);
96     }
97 
98     asyncContext->status = static_cast<UiAppearanceAbilityErrCode>(resCode);
99     if (asyncContext->status == UiAppearanceAbilityErrCode::PERMISSION_ERR) {
100         asyncContext->errMsg = PERMISSION_ERR_MSG;
101     } else if (asyncContext->status == UiAppearanceAbilityErrCode::INVALID_ARG) {
102         asyncContext->errMsg = "fontScale must between 0 and 5";
103     } else {
104         asyncContext->errMsg = "";
105     }
106 }
107 
OnSetFontWeightScale(napi_env env,void * data)108 void JsUiAppearance::OnSetFontWeightScale(napi_env env, void* data)
109 {
110     LOGI("OnSetFontWeightScale begin.");
111     AsyncContext* asyncContext = static_cast<AsyncContext*>(data);
112     if (asyncContext == nullptr) {
113         NapiThrow(env, "asyncContext is null.", UiAppearanceAbilityErrCode::SYS_ERR);
114         return;
115     }
116     int32_t resCode = 0;
117     if (asyncContext->jsFontWeightScale <= MIN_FONT_SCALE ||
118         asyncContext->jsFontWeightScale > MAX_FONT_SCALE) {
119         resCode = UiAppearanceAbilityErrCode::INVALID_ARG;
120     } else {
121         resCode = UiAppearanceAbilityClient::GetInstance()
122             ->SetFontWeightScale(asyncContext->fontWeightScale);
123     }
124 
125     asyncContext->status = static_cast<UiAppearanceAbilityErrCode>(resCode);
126     if (asyncContext->status == UiAppearanceAbilityErrCode::PERMISSION_ERR) {
127         asyncContext->errMsg = PERMISSION_ERR_MSG;
128     } else if (asyncContext->status == UiAppearanceAbilityErrCode::INVALID_ARG) {
129         asyncContext->errMsg = "fontWeightScale must between 0 and 5";
130     } else {
131         asyncContext->errMsg = "";
132     }
133 }
134 
OnComplete(napi_env env,napi_status status,void * data)135 void JsUiAppearance::OnComplete(napi_env env, napi_status status, void* data)
136 {
137     LOGI("OnComplete begin.");
138     napi_handle_scope scope = nullptr;
139     napi_open_handle_scope(env, &scope);
140     if (scope == nullptr) {
141         NapiThrow(env, "open handle scope failed.", UiAppearanceAbilityErrCode::SYS_ERR);
142         return;
143     }
144     AsyncContext* asyncContext = static_cast<AsyncContext*>(data);
145     if (asyncContext == nullptr) {
146         napi_close_handle_scope(env, scope);
147         NapiThrow(env, "asyncContext is null.", UiAppearanceAbilityErrCode::SYS_ERR);
148         return;
149     }
150 
151     if (asyncContext->status == UiAppearanceAbilityErrCode::SUCCEEDED) {
152         napi_value result = nullptr;
153         napi_get_null(env, &result);
154         if (asyncContext->deferred) { // promise
155             napi_resolve_deferred(env, asyncContext->deferred, result);
156         } else { // AsyncCallback
157             napi_value callback = nullptr;
158             napi_get_reference_value(env, asyncContext->callbackRef, &callback);
159             napi_value ret;
160             napi_call_function(env, nullptr, callback, 1, &result, &ret);
161         }
162     } else {
163         napi_value code = nullptr;
164         std::string strCode = std::to_string(asyncContext->status);
165         napi_create_string_utf8(env, strCode.c_str(), strCode.length(), &code);
166 
167         napi_value msg = nullptr;
168         std::string strMsg = ParseErrCode(asyncContext->status) + asyncContext->errMsg;
169         LOGI("napi throw errCode %{public}d, strMsg %{public}s", asyncContext->status, strMsg.c_str());
170         napi_create_string_utf8(env, strMsg.c_str(), strMsg.length(), &msg);
171 
172         napi_value error = nullptr;
173         napi_create_error(env, code, msg, &error);
174         if (asyncContext->deferred) { // promise
175             napi_reject_deferred(env, asyncContext->deferred, error);
176         } else { // AsyncCallback
177             napi_value callback = nullptr;
178             napi_get_reference_value(env, asyncContext->callbackRef, &callback);
179             napi_value ret;
180             napi_call_function(env, nullptr, callback, 1, &error, &ret);
181         }
182     }
183     napi_delete_async_work(env, asyncContext->work);
184     delete asyncContext;
185     napi_close_handle_scope(env, scope);
186 }
187 
CheckArgs(napi_env env,size_t argc,napi_value * argv)188 napi_status JsUiAppearance::CheckArgs(napi_env env, size_t argc, napi_value* argv)
189 {
190     if (argc != ARGC_WITH_ONE && argc != ARGC_WITH_TWO) {
191         NapiThrow(
192             env, "the number of parameters can only be 1 or 2.", UiAppearanceAbilityErrCode::INVALID_ARG);
193         return napi_invalid_arg;
194     }
195 
196     napi_valuetype valueType = napi_undefined;
197     switch (argc) {
198         case ARGC_WITH_TWO:
199             napi_typeof(env, argv[1], &valueType);
200             if (valueType != napi_function) {
201                 NapiThrow(env, "the second parameter must be a function.",
202                     UiAppearanceAbilityErrCode::INVALID_ARG);
203                 return napi_invalid_arg;
204             }
205             [[fallthrough]];
206         case ARGC_WITH_ONE:
207             napi_typeof(env, argv[0], &valueType);
208             if (valueType != napi_number) {
209                 NapiThrow(
210                     env, "the first parameter must be DarkMode.", UiAppearanceAbilityErrCode::INVALID_ARG);
211                 return napi_invalid_arg;
212             }
213             break;
214         default:
215             return napi_invalid_arg;
216     }
217     return napi_ok;
218 }
219 
CheckFontScaleArgs(napi_env env,size_t argc,napi_value * argv)220 napi_status JsUiAppearance::CheckFontScaleArgs(napi_env env, size_t argc, napi_value* argv)
221 {
222     if (argc != ARGC_WITH_ONE && argc != ARGC_WITH_TWO) {
223         NapiThrow(
224             env, "the number of parameters can only be 1 or 2.", UiAppearanceAbilityErrCode::INVALID_ARG);
225         return napi_invalid_arg;
226     }
227 
228     napi_valuetype valueType = napi_undefined;
229     switch (argc) {
230         case ARGC_WITH_TWO:
231             napi_typeof(env, argv[1], &valueType);
232             if (valueType != napi_function) {
233                 NapiThrow(env, "the second parameter must be a function.",
234                     UiAppearanceAbilityErrCode::INVALID_ARG);
235                 return napi_invalid_arg;
236             }
237             [[fallthrough]];
238         case ARGC_WITH_ONE:
239             napi_typeof(env, argv[0], &valueType);
240             if (valueType != napi_number) {
241                 NapiThrow(
242                     env, "the first parameter must be Number.", UiAppearanceAbilityErrCode::INVALID_ARG);
243                 return napi_invalid_arg;
244             }
245             break;
246         default:
247             return napi_invalid_arg;
248     }
249     return napi_ok;
250 }
251 
ConvertJsDarkMode2Enum(int32_t jsVal)252 DarkMode JsUiAppearance::ConvertJsDarkMode2Enum(int32_t jsVal)
253 {
254     switch (jsVal) {
255         case 0:
256             return DarkMode::ALWAYS_DARK;
257         case 1:
258             return DarkMode::ALWAYS_LIGHT;
259         default:
260             return DarkMode::UNKNOWN;
261     }
262 }
263 
JSSetDarkMode(napi_env env,napi_callback_info info)264 static napi_value JSSetDarkMode(napi_env env, napi_callback_info info)
265 {
266     LOGI("JSSetDarkMode begin.");
267 
268     size_t argc = ARGC_WITH_TWO;
269     napi_value argv[ARGC_WITH_TWO] = { 0 };
270     napi_status napiStatus = napi_ok;
271     napi_value result = nullptr;
272     napi_get_undefined(env, &result);
273 
274     napiStatus = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
275     if (napiStatus != napi_ok) {
276         NapiThrow(env, "get callback info failed.", UiAppearanceAbilityErrCode::INVALID_ARG);
277         return result;
278     }
279     napiStatus = JsUiAppearance::CheckArgs(env, argc, argv);
280     if (napiStatus != napi_ok) {
281         NapiThrow(env, "parameter parsing error.", UiAppearanceAbilityErrCode::INVALID_ARG);
282         return result;
283     }
284 
285     auto asyncContext = new (std::nothrow) AsyncContext();
286     if (asyncContext == nullptr) {
287         NapiThrow(env, "create AsyncContext failed.", UiAppearanceAbilityErrCode::SYS_ERR);
288         return result;
289     }
290 
291     napi_get_value_int32(env, argv[0], &asyncContext->jsSetArg);
292     asyncContext->mode = JsUiAppearance::ConvertJsDarkMode2Enum(asyncContext->jsSetArg);
293     if (argc == ARGC_WITH_TWO) {
294         napi_create_reference(env, argv[1], 1, &asyncContext->callbackRef);
295     }
296     if (asyncContext->callbackRef == nullptr) {
297         napi_create_promise(env, &asyncContext->deferred, &result);
298     }
299 
300     napi_value resource = nullptr;
301     napi_create_string_utf8(env, "JSSetDarkMode", NAPI_AUTO_LENGTH, &resource);
302     napi_create_async_work(env, nullptr, resource, JsUiAppearance::OnExecute, JsUiAppearance::OnComplete,
303         reinterpret_cast<void*>(asyncContext), &asyncContext->work);
304     napi_queue_async_work(env, asyncContext->work);
305 
306     return result;
307 }
308 
JSGetDarkMode(napi_env env,napi_callback_info info)309 static napi_value JSGetDarkMode(napi_env env, napi_callback_info info)
310 {
311     LOGI("JSGetDarkMode begin.");
312 
313     napi_value result = nullptr;
314     napi_get_undefined(env, &result);
315     size_t argc = 0;
316     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr));
317     if (argc != 0) {
318         NapiThrow(env, "requires no parameter.", UiAppearanceAbilityErrCode::INVALID_ARG);
319         return result;
320     }
321 
322     auto mode = UiAppearanceAbilityClient::GetInstance()->GetDarkMode();
323     if (mode == UiAppearanceAbilityErrCode::SYS_ERR) {
324         NapiThrow(env, "get dark-mode failed.", UiAppearanceAbilityErrCode::SYS_ERR);
325         return result;
326     }
327 
328     NAPI_CALL(env, napi_create_int32(env, mode, &result));
329     return result;
330 }
331 
JSGetFontScale(napi_env env,napi_callback_info info)332 static napi_value JSGetFontScale(napi_env env, napi_callback_info info)
333 {
334     LOGI("JSGetFontScale begin.");
335     napi_value result = nullptr;
336     napi_get_undefined(env, &result);
337     size_t argc = 0;
338     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr));
339     if (argc != 0) {
340         NapiThrow(env, "requires no parameter.", UiAppearanceAbilityErrCode::INVALID_ARG);
341         return result;
342     }
343 
344     std::string fontScale;
345     auto ret = UiAppearanceAbilityClient::GetInstance()->GetFontScale(fontScale);
346     if (ret == UiAppearanceAbilityErrCode::SYS_ERR) {
347         NapiThrow(env, "get font-scale failed.", UiAppearanceAbilityErrCode::SYS_ERR);
348         return result;
349     }
350 
351     double fontScaleNumber = std::stod(fontScale);
352     NAPI_CALL(env, napi_create_double(env, fontScaleNumber, &result));
353     return result;
354 }
355 
JSSetFontScale(napi_env env,napi_callback_info info)356 static napi_value JSSetFontScale(napi_env env, napi_callback_info info)
357 {
358     LOGI("JSSetFontScale begin.");
359 
360     size_t argc = ARGC_WITH_TWO;
361     napi_value argv[ARGC_WITH_TWO] = { 0 };
362     napi_status napiStatus = napi_ok;
363     napi_value result = nullptr;
364     napi_get_undefined(env, &result);
365 
366     napiStatus = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
367     if (napiStatus != napi_ok) {
368         NapiThrow(env, "get callback info failed.", UiAppearanceAbilityErrCode::INVALID_ARG);
369         return result;
370     }
371     napiStatus = JsUiAppearance::CheckFontScaleArgs(env, argc, argv);
372     if (napiStatus != napi_ok) {
373         NapiThrow(env, "parameter parsing error.", UiAppearanceAbilityErrCode::INVALID_ARG);
374         return result;
375     }
376     auto asyncContext = new (std::nothrow) AsyncContext();
377     if (asyncContext == nullptr) {
378         NapiThrow(env, "create AsyncContext failed.", UiAppearanceAbilityErrCode::SYS_ERR);
379         return result;
380     }
381     napi_get_value_double(env, argv[0], &asyncContext->jsFontScale);
382 
383     asyncContext->fontScale = std::to_string(asyncContext->jsFontScale);
384     if (argc == ARGC_WITH_TWO) {
385         napi_create_reference(env, argv[1], 1, &asyncContext->callbackRef);
386     }
387     if (asyncContext->callbackRef == nullptr) {
388         napi_create_promise(env, &asyncContext->deferred, &result);
389     }
390 
391     napi_value resource = nullptr;
392     napi_create_string_utf8(env, "JSSetFontScale", NAPI_AUTO_LENGTH, &resource);
393     napi_create_async_work(env, nullptr, resource, JsUiAppearance::OnSetFontScale, JsUiAppearance::OnComplete,
394         reinterpret_cast<void*>(asyncContext), &asyncContext->work);
395     napi_queue_async_work(env, asyncContext->work);
396 
397     return result;
398 }
399 
JSGetFontWeightScale(napi_env env,napi_callback_info info)400 static napi_value JSGetFontWeightScale(napi_env env, napi_callback_info info)
401 {
402     LOGI("JSGetFontWeightScale begin.");
403     napi_value result = nullptr;
404     napi_get_undefined(env, &result);
405     size_t argc = 0;
406     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr));
407     if (argc != 0) {
408         NapiThrow(env, "requires no parameter.", UiAppearanceAbilityErrCode::INVALID_ARG);
409         return result;
410     }
411 
412     std::string fontWeightScale;
413     auto ret = UiAppearanceAbilityClient::GetInstance()->GetFontWeightScale(fontWeightScale);
414     if (ret == UiAppearanceAbilityErrCode::SYS_ERR) {
415         NapiThrow(env, "get font-Weight-scale failed.", UiAppearanceAbilityErrCode::SYS_ERR);
416         return result;
417     }
418 
419     double fontWeightScaleNumber = std::stod(fontWeightScale);
420     NAPI_CALL(env, napi_create_double(env, fontWeightScaleNumber, &result));
421     return result;
422 }
423 
JSSetFontWeightScale(napi_env env,napi_callback_info info)424 static napi_value JSSetFontWeightScale(napi_env env, napi_callback_info info)
425 {
426     LOGI("JSSetFontWeightScale begin.");
427 
428     size_t argc = ARGC_WITH_TWO;
429     napi_value argv[ARGC_WITH_TWO] = { 0 };
430     napi_status napiStatus = napi_ok;
431     napi_value result = nullptr;
432     napi_get_undefined(env, &result);
433 
434     napiStatus = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
435     if (napiStatus != napi_ok) {
436         NapiThrow(env, "get callback info failed.", UiAppearanceAbilityErrCode::INVALID_ARG);
437         return result;
438     }
439     napiStatus = JsUiAppearance::CheckFontScaleArgs(env, argc, argv);
440     if (napiStatus != napi_ok) {
441         NapiThrow(env, "parameter parsing error.", UiAppearanceAbilityErrCode::INVALID_ARG);
442         return result;
443     }
444     auto asyncContext = new (std::nothrow) AsyncContext();
445     if (asyncContext == nullptr) {
446         NapiThrow(env, "create AsyncContext failed.", UiAppearanceAbilityErrCode::SYS_ERR);
447         return result;
448     }
449     napi_get_value_double(env, argv[0], &asyncContext->jsFontWeightScale);
450 
451     asyncContext->fontWeightScale = std::to_string(asyncContext->jsFontWeightScale);
452     if (argc == ARGC_WITH_TWO) {
453         napi_create_reference(env, argv[1], 1, &asyncContext->callbackRef);
454     }
455     if (asyncContext->callbackRef == nullptr) {
456         napi_create_promise(env, &asyncContext->deferred, &result);
457     }
458 
459     napi_value resource = nullptr;
460     napi_create_string_utf8(env, "JSSetFontWeightScale", NAPI_AUTO_LENGTH, &resource);
461     napi_create_async_work(env, nullptr, resource, JsUiAppearance::OnSetFontWeightScale, JsUiAppearance::OnComplete,
462         reinterpret_cast<void*>(asyncContext), &asyncContext->work);
463     napi_queue_async_work(env, asyncContext->work);
464 
465     return result;
466 }
467 
468 EXTERN_C_START
UiAppearanceExports(napi_env env,napi_value exports)469 static napi_value UiAppearanceExports(napi_env env, napi_value exports)
470 {
471     napi_value DarkMode = nullptr;
472     napi_value alwaysDark = nullptr;
473     napi_value alwaysLight = nullptr;
474     NAPI_CALL(env, napi_create_int32(env, 0, &alwaysDark));
475     NAPI_CALL(env, napi_create_int32(env, 1, &alwaysLight));
476     NAPI_CALL(env, napi_create_object(env, &DarkMode));
477     NAPI_CALL(env, napi_set_named_property(env, DarkMode, "ALWAYS_DARK", alwaysDark));
478     NAPI_CALL(env, napi_set_named_property(env, DarkMode, "ALWAYS_LIGHT", alwaysLight));
479     napi_property_descriptor properties[] = {
480         DECLARE_NAPI_FUNCTION("setDarkMode", JSSetDarkMode),
481         DECLARE_NAPI_FUNCTION("getDarkMode", JSGetDarkMode),
482         DECLARE_NAPI_FUNCTION("getFontScale", JSGetFontScale),
483         DECLARE_NAPI_FUNCTION("setFontScale", JSSetFontScale),
484         DECLARE_NAPI_FUNCTION("getFontWeightScale", JSGetFontWeightScale),
485         DECLARE_NAPI_FUNCTION("setFontWeightScale", JSSetFontWeightScale),
486         DECLARE_NAPI_STATIC_PROPERTY("DarkMode", DarkMode),
487     };
488     NAPI_CALL(env, napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties));
489     return exports;
490 }
491 EXTERN_C_END
492 
493 static napi_module ui_appearance_module = {
494     .nm_version = 1,
495     .nm_flags = 0,
496     .nm_filename = nullptr,
497     .nm_register_func = UiAppearanceExports,
498     .nm_modname = "uiAppearance", // relative to the module name while import.
499     .nm_priv = nullptr,
500     .reserved = { 0 },
501 };
502 
UiAppearanceRegister()503 extern "C" __attribute__((constructor)) void UiAppearanceRegister()
504 {
505     napi_module_register(&ui_appearance_module);
506 }
507 } // namespace ArkUi::UiAppearance
508 } // namespace OHOS
509