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