1 /*
2 * Copyright (c) 2021-2022 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 "frameworks/bridge/declarative_frontend/jsview/dialog/js_alert_dialog.h"
16
17 #include <sstream>
18 #include <string>
19 #include <vector>
20
21 #include "base/log/ace_scoring_log.h"
22 #include "bridge/declarative_frontend/jsview/models/alert_dialog_model_impl.h"
23 #include "core/common/container.h"
24 #include "core/components_ng/base/view_stack_processor.h"
25 #include "core/components_ng/pattern/dialog/alert_dialog_model_ng.h"
26 #include "frameworks/bridge/common/utils/engine_helper.h"
27 #include "frameworks/bridge/declarative_frontend/engine/functions/js_function.h"
28
29 namespace OHOS::Ace {
30 std::unique_ptr<AlertDialogModel> AlertDialogModel::instance_ = nullptr;
31 std::mutex AlertDialogModel::mutex_;
GetInstance()32 AlertDialogModel* AlertDialogModel::GetInstance()
33 {
34 if (!instance_) {
35 std::lock_guard<std::mutex> lock(mutex_);
36 if (!instance_) {
37 #ifdef NG_BUILD
38 instance_.reset(new NG::AlertDialogModelNG());
39 #else
40 if (Container::IsCurrentUseNewPipeline()) {
41 instance_.reset(new NG::AlertDialogModelNG());
42 } else {
43 instance_.reset(new Framework::AlertDialogModelImpl());
44 }
45 #endif
46 }
47 }
48 return instance_.get();
49 }
50
51 } // namespace OHOS::Ace
52 namespace OHOS::Ace::Framework {
53 namespace {
54 const std::vector<DialogAlignment> DIALOG_ALIGNMENT = { DialogAlignment::TOP, DialogAlignment::CENTER,
55 DialogAlignment::BOTTOM, DialogAlignment::DEFAULT, DialogAlignment::TOP_START, DialogAlignment::TOP_END,
56 DialogAlignment::CENTER_START, DialogAlignment::CENTER_END, DialogAlignment::BOTTOM_START,
57 DialogAlignment::BOTTOM_END };
58 const std::vector<DialogButtonDirection> DIALOG_BUTTONS_DIRECTION = { DialogButtonDirection::AUTO,
59 DialogButtonDirection::HORIZONTAL, DialogButtonDirection::VERTICAL };
60 constexpr int32_t ALERT_DIALOG_VALID_PRIMARY_BUTTON_NUM = 1;
61 } // namespace
62
SetParseStyle(ButtonInfo & buttonInfo,const int32_t styleValue)63 void SetParseStyle(ButtonInfo& buttonInfo, const int32_t styleValue)
64 {
65 if (styleValue >= static_cast<int32_t>(DialogButtonStyle::DEFAULT) &&
66 styleValue <= static_cast<int32_t>(DialogButtonStyle::HIGHTLIGHT)) {
67 buttonInfo.dlgButtonStyle = static_cast<DialogButtonStyle>(styleValue);
68 }
69 }
70
ParseButtonObj(const JsiExecutionContext & execContext,DialogProperties & properties,JSRef<JSVal> jsVal,const std::string & property,bool isPrimaryButtonValid)71 void ParseButtonObj(const JsiExecutionContext& execContext, DialogProperties& properties, JSRef<JSVal> jsVal,
72 const std::string& property, bool isPrimaryButtonValid)
73 {
74 if (!jsVal->IsObject()) {
75 return;
76 }
77 auto objInner = JSRef<JSObject>::Cast(jsVal);
78 std::string buttonValue;
79 ButtonInfo buttonInfo;
80 if (JSAlertDialog::ParseJsString(objInner->GetProperty("value"), buttonValue)) {
81 buttonInfo.text = buttonValue;
82 }
83
84 // Parse enabled
85 auto enabledValue = objInner->GetProperty("enabled");
86 if (enabledValue->IsBoolean()) {
87 buttonInfo.enabled = enabledValue->ToBoolean();
88 }
89
90 // Parse defaultFocus
91 auto defaultFocusValue = objInner->GetProperty("defaultFocus");
92 if (defaultFocusValue->IsBoolean()) {
93 buttonInfo.defaultFocus = defaultFocusValue->ToBoolean();
94 }
95
96 // Parse style
97 auto style = objInner->GetProperty("style");
98 if (style->IsNumber()) {
99 SetParseStyle(buttonInfo, style->ToNumber<int32_t>());
100 }
101
102 Color textColor;
103 if (JSAlertDialog::ParseJsColor(objInner->GetProperty("fontColor"), textColor)) {
104 buttonInfo.textColor = textColor.ColorToString();
105 }
106
107 Color backgroundColor;
108 if (JSAlertDialog::ParseJsColor(objInner->GetProperty("backgroundColor"), backgroundColor)) {
109 buttonInfo.isBgColorSetted = true;
110 buttonInfo.bgColor = backgroundColor;
111 }
112
113 auto actionValue = objInner->GetProperty("action");
114 if (actionValue->IsFunction()) {
115 auto frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
116 auto actionFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(actionValue));
117 auto eventFunc = [execCtx = execContext, func = std::move(actionFunc), property, node = frameNode]() {
118 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
119 ACE_SCORING_EVENT("AlertDialog.[" + property + "].onAction");
120 auto pipelineContext = PipelineContext::GetCurrentContextSafely();
121 CHECK_NULL_VOID(pipelineContext);
122 pipelineContext->UpdateCurrentActiveNode(node);
123 func->Execute();
124 };
125 AlertDialogModel::GetInstance()->SetParseButtonObj(eventFunc, buttonInfo, properties, property);
126 }
127
128 if (!buttonInfo.defaultFocus && isPrimaryButtonValid) {
129 if (strcmp(property.c_str(), "confirm") == 0 ||
130 strcmp(property.c_str(), "primaryButton") == 0) {
131 buttonInfo.isPrimary = true;
132 } else {
133 auto primaryButton = objInner->GetProperty("primary");
134 if (primaryButton->IsBoolean()) {
135 buttonInfo.isPrimary = primaryButton->ToBoolean();
136 }
137 }
138 }
139
140 if (buttonInfo.IsValid()) {
141 properties.buttons.emplace_back(buttonInfo);
142 }
143 }
144
ParseButtonArray(const JsiExecutionContext & execContext,DialogProperties & properties,JSRef<JSObject> obj,const std::string & property)145 void ParseButtonArray(const JsiExecutionContext& execContext, DialogProperties& properties, JSRef<JSObject> obj,
146 const std::string& property)
147 {
148 auto jsVal = obj->GetProperty(property.c_str());
149 if (!jsVal->IsArray()) {
150 return;
151 }
152 JSRef<JSArray> array = JSRef<JSArray>::Cast(jsVal);
153 size_t length = array->Length();
154 if (length <= 0) {
155 return;
156 }
157 int32_t primaryButtonNum = 0;
158 bool isPrimaryButtonValid = true;
159 for (size_t i = 0; i < length; i++) {
160 JSRef<JSVal> buttonItem = array->GetValueAt(i);
161 if (!buttonItem->IsObject()) {
162 break;
163 }
164 auto objInner = JSRef<JSObject>::Cast(buttonItem);
165 auto primaryButton = objInner->GetProperty("primary");
166 if (primaryButton->IsBoolean()) {
167 primaryButtonNum += (primaryButton->ToBoolean() ? ALERT_DIALOG_VALID_PRIMARY_BUTTON_NUM : 0);
168 }
169 if (primaryButtonNum > ALERT_DIALOG_VALID_PRIMARY_BUTTON_NUM) {
170 isPrimaryButtonValid = false;
171 break;
172 }
173 }
174 for (size_t i = 0; i < length; i++) {
175 JSRef<JSVal> buttonItem = array->GetValueAt(i);
176 if (!buttonItem->IsObject()) {
177 break;
178 }
179 ParseButtonObj(execContext, properties, buttonItem, property + std::to_string(i), isPrimaryButtonValid);
180 }
181 }
182
ParseButtons(const JsiExecutionContext & execContext,DialogProperties & properties,JSRef<JSObject> obj)183 void ParseButtons(const JsiExecutionContext& execContext, DialogProperties& properties, JSRef<JSObject> obj)
184 {
185 properties.buttons.clear();
186 if (obj->GetProperty("confirm")->IsObject()) {
187 // Parse confirm.
188 auto objInner = obj->GetProperty("confirm");
189 ParseButtonObj(execContext, properties, objInner, "confirm", true);
190 } else if (obj->GetProperty("buttons")->IsArray()) {
191 // Parse buttons array.
192 ParseButtonArray(execContext, properties, obj, "buttons");
193 } else {
194 // Parse primaryButton and secondaryButton.
195 auto objInner = obj->GetProperty("primaryButton");
196 ParseButtonObj(execContext, properties, objInner, "primaryButton", true);
197 objInner = obj->GetProperty("secondaryButton");
198 ParseButtonObj(execContext, properties, objInner, "secondaryButton", true);
199 }
200
201 // Parse buttons direction.
202 auto directionValue = obj->GetProperty("buttonDirection");
203 if (directionValue->IsNumber()) {
204 auto buttonDirection = directionValue->ToNumber<int32_t>();
205 if (buttonDirection >= 0 && buttonDirection <= static_cast<int32_t>(DIALOG_BUTTONS_DIRECTION.size())) {
206 properties.buttonDirection = DIALOG_BUTTONS_DIRECTION[buttonDirection];
207 }
208 }
209 }
210
ParseDialogTitleAndMessage(DialogProperties & properties,JSRef<JSObject> obj)211 void ParseDialogTitleAndMessage(DialogProperties& properties, JSRef<JSObject> obj)
212 {
213 // Parse title.
214 auto titleValue = obj->GetProperty("title");
215 std::string title;
216 if (JSAlertDialog::ParseJsString(titleValue, title)) {
217 properties.title = title;
218 }
219
220 // Parse subtitle.
221 auto subtitleValue = obj->GetProperty("subtitle");
222 std::string subtitle;
223 if (JSAlertDialog::ParseJsString(subtitleValue, subtitle)) {
224 properties.subtitle = subtitle;
225 }
226
227 // Parses message.
228 auto messageValue = obj->GetProperty("message");
229 std::string message;
230 if (JSAlertDialog::ParseJsString(messageValue, message)) {
231 properties.content = message;
232 }
233 }
234
ParseTextStyle(DialogProperties & properties,JSRef<JSObject> obj)235 void ParseTextStyle(DialogProperties& properties, JSRef<JSObject> obj)
236 {
237 auto textStyleObj = obj->GetProperty("textStyle");
238 if (textStyleObj->IsNull() || !textStyleObj->IsObject()) {
239 return;
240 }
241 auto textStyle = JSRef<JSObject>::Cast(textStyleObj);
242 auto args = textStyle->GetProperty("wordBreak");
243 int32_t index = 1;
244 if (args->IsNumber()) {
245 index = args->ToNumber<int32_t>();
246 }
247 if (index < 0 || index >= static_cast<int32_t>(WORD_BREAK_TYPES.size())) {
248 index = 1;
249 }
250 properties.wordBreak = WORD_BREAK_TYPES[index];
251 }
252
ParseAlertShadow(DialogProperties & properties,JSRef<JSObject> obj)253 void ParseAlertShadow(DialogProperties& properties, JSRef<JSObject> obj)
254 {
255 // Parse shadow.
256 auto shadowValue = obj->GetProperty("shadow");
257 Shadow shadow;
258 if ((shadowValue->IsObject() || shadowValue->IsNumber()) && JSAlertDialog::ParseShadowProps(shadowValue, shadow)) {
259 properties.shadow = shadow;
260 }
261 }
262
ParseAlertBorderWidthAndColor(DialogProperties & properties,JSRef<JSObject> obj)263 void ParseAlertBorderWidthAndColor(DialogProperties& properties, JSRef<JSObject> obj)
264 {
265 auto borderWidthValue = obj->GetProperty("borderWidth");
266 NG::BorderWidthProperty borderWidth;
267 if (JSAlertDialog::ParseBorderWidthProps(borderWidthValue, borderWidth)) {
268 properties.borderWidth = borderWidth;
269 auto colorValue = obj->GetProperty("borderColor");
270 NG::BorderColorProperty borderColor;
271 if (JSAlertDialog::ParseBorderColorProps(colorValue, borderColor)) {
272 properties.borderColor = borderColor;
273 } else {
274 borderColor.SetColor(Color::BLACK);
275 properties.borderColor = borderColor;
276 }
277 }
278 }
279
UpdateAlertAlignment(DialogAlignment & alignment)280 void UpdateAlertAlignment(DialogAlignment& alignment)
281 {
282 bool isRtl = AceApplicationInfo::GetInstance().IsRightToLeft();
283 if (alignment == DialogAlignment::TOP_START) {
284 if (isRtl) {
285 alignment = DialogAlignment::TOP_END;
286 }
287 } else if (alignment == DialogAlignment::TOP_END) {
288 if (isRtl) {
289 alignment = DialogAlignment::TOP_START;
290 }
291 } else if (alignment == DialogAlignment::CENTER_START) {
292 if (isRtl) {
293 alignment = DialogAlignment::CENTER_END;
294 }
295 } else if (alignment == DialogAlignment::CENTER_END) {
296 if (isRtl) {
297 alignment = DialogAlignment::CENTER_START;
298 }
299 } else if (alignment == DialogAlignment::BOTTOM_START) {
300 if (isRtl) {
301 alignment = DialogAlignment::BOTTOM_END;
302 }
303 } else if (alignment == DialogAlignment::BOTTOM_END) {
304 if (isRtl) {
305 alignment = DialogAlignment::BOTTOM_START;
306 }
307 }
308 }
309
ParseAlertRadius(DialogProperties & properties,JSRef<JSObject> obj)310 void ParseAlertRadius(DialogProperties& properties, JSRef<JSObject> obj)
311 {
312 auto cornerRadiusValue = obj->GetProperty("cornerRadius");
313 NG::BorderRadiusProperty radius;
314 if (JSAlertDialog::ParseBorderRadius(cornerRadiusValue, radius)) {
315 properties.borderRadius = radius;
316 }
317 }
318
ParseAlertAlignment(DialogProperties & properties,JSRef<JSObject> obj)319 void ParseAlertAlignment(DialogProperties& properties, JSRef<JSObject> obj)
320 {
321 // Parse alignment
322 auto alignmentValue = obj->GetProperty("alignment");
323 if (alignmentValue->IsNumber()) {
324 auto alignment = alignmentValue->ToNumber<int32_t>();
325 if (alignment >= 0 && alignment <= static_cast<int32_t>(DIALOG_ALIGNMENT.size())) {
326 properties.alignment = DIALOG_ALIGNMENT[alignment];
327 UpdateAlertAlignment(properties.alignment);
328 }
329 }
330 }
331
ParseAlertOffset(DialogProperties & properties,JSRef<JSObject> obj)332 void ParseAlertOffset(DialogProperties& properties, JSRef<JSObject> obj)
333 {
334 // Parse offset
335 auto offsetValue = obj->GetProperty("offset");
336 if (offsetValue->IsObject()) {
337 auto offsetObj = JSRef<JSObject>::Cast(offsetValue);
338 CalcDimension dx;
339 auto dxValue = offsetObj->GetProperty("dx");
340 JSAlertDialog::ParseJsDimensionVp(dxValue, dx);
341 CalcDimension dy;
342 auto dyValue = offsetObj->GetProperty("dy");
343 JSAlertDialog::ParseJsDimensionVp(dyValue, dy);
344 properties.offset = DimensionOffset(dx, dy);
345 bool isRtl = AceApplicationInfo::GetInstance().IsRightToLeft();
346 Dimension offsetX = isRtl ? properties.offset.GetX() * (-1) : properties.offset.GetX();
347 properties.offset.SetX(offsetX);
348 }
349 }
350
ParseAlertMaskRect(DialogProperties & properties,JSRef<JSObject> obj)351 void ParseAlertMaskRect(DialogProperties& properties, JSRef<JSObject> obj)
352 {
353 // Parse maskRect.
354 auto maskRectValue = obj->GetProperty("maskRect");
355 DimensionRect maskRect;
356 if (JSViewAbstract::ParseJsDimensionRect(maskRectValue, maskRect)) {
357 properties.maskRect = maskRect;
358 bool isRtl = AceApplicationInfo::GetInstance().IsRightToLeft();
359 auto offset = maskRect.GetOffset();
360 Dimension offsetX = isRtl ? offset.GetX() * (-1) : offset.GetX();
361 offset.SetX(offsetX);
362 properties.maskRect->SetOffset(offset);
363 }
364 }
365
Show(const JSCallbackInfo & args)366 void JSAlertDialog::Show(const JSCallbackInfo& args)
367 {
368 auto scopedDelegate = EngineHelper::GetCurrentDelegateSafely();
369 if (!scopedDelegate) {
370 // this case usually means there is no foreground container, need to figure out the reason.
371 LOGE("scopedDelegate is null, please check");
372 return;
373 }
374
375 DialogProperties properties { .type = DialogType::ALERT_DIALOG };
376 if (args[0]->IsObject()) {
377 auto obj = JSRef<JSObject>::Cast(args[0]);
378 auto dialogNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
379 auto execContext = args.GetExecutionContext();
380
381 ParseDialogTitleAndMessage(properties, obj);
382 ParseButtons(execContext, properties, obj);
383 ParseTextStyle(properties, obj);
384 ParseAlertShadow(properties, obj);
385 ParseAlertBorderWidthAndColor(properties, obj);
386 ParseAlertRadius(properties, obj);
387 ParseAlertAlignment(properties, obj);
388 ParseAlertOffset(properties, obj);
389 ParseAlertMaskRect(properties, obj);
390
391 auto onLanguageChange = [execContext, obj, parseContent = ParseDialogTitleAndMessage,
392 parseButton = ParseButtons, parseShadow = ParseAlertShadow,
393 parseBorderProps = ParseAlertBorderWidthAndColor,
394 parseRadius = ParseAlertRadius, parseAlignment = ParseAlertAlignment,
395 parseOffset = ParseAlertOffset, parseMaskRect = ParseAlertMaskRect,
396 node = dialogNode](DialogProperties& dialogProps) {
397 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execContext);
398 ACE_SCORING_EVENT("AlertDialog.property.onLanguageChange");
399 auto pipelineContext = PipelineContext::GetCurrentContextSafely();
400 CHECK_NULL_VOID(pipelineContext);
401 pipelineContext->UpdateCurrentActiveNode(node);
402 parseContent(dialogProps, obj);
403 parseButton(execContext, dialogProps, obj);
404 parseShadow(dialogProps, obj);
405 parseBorderProps(dialogProps, obj);
406 parseRadius(dialogProps, obj);
407 parseAlignment(dialogProps, obj);
408 parseOffset(dialogProps, obj);
409 parseMaskRect(dialogProps, obj);
410 };
411 properties.onLanguageChange = std::move(onLanguageChange);
412
413 // Parses gridCount.
414 auto gridCountValue = obj->GetProperty("gridCount");
415 if (gridCountValue->IsNumber()) {
416 properties.gridCount = gridCountValue->ToNumber<int32_t>();
417 }
418
419 // Parse auto autoCancel.
420 auto autoCancelValue = obj->GetProperty("autoCancel");
421 if (autoCancelValue->IsBoolean()) {
422 properties.autoCancel = autoCancelValue->ToBoolean();
423 }
424
425 // Parse cancel.
426 auto cancelValue = obj->GetProperty("cancel");
427 if (cancelValue->IsFunction()) {
428 auto cancelFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(cancelValue));
429 auto eventFunc = [execContext, func = std::move(cancelFunc), node = dialogNode]() {
430 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execContext);
431 ACE_SCORING_EVENT("AlertDialog.property.cancel");
432 auto pipelineContext = PipelineContext::GetCurrentContextSafely();
433 CHECK_NULL_VOID(pipelineContext);
434 pipelineContext->UpdateCurrentActiveNode(node);
435 func->Execute();
436 };
437 AlertDialogModel::GetInstance()->SetOnCancel(eventFunc, properties);
438 }
439
440 std::function<void(const int32_t& info)> onWillDismissFunc = nullptr;
441 ParseDialogCallback(obj, onWillDismissFunc);
442 AlertDialogModel::GetInstance()->SetOnWillDismiss(std::move(onWillDismissFunc), properties);
443
444 // Parse showInSubWindowValue.
445 auto showInSubWindowValue = obj->GetProperty("showInSubWindow");
446 if (showInSubWindowValue->IsBoolean()) {
447 #if defined(PREVIEW)
448 LOGW("[Engine Log] Unable to use the SubWindow in the Previewer. Perform this operation on the "
449 "emulator or a real device instead.");
450 #else
451 properties.isShowInSubWindow = showInSubWindowValue->ToBoolean();
452 #endif
453 }
454
455 // Parse isModal.
456 auto isModalValue = obj->GetProperty("isModal");
457 if (isModalValue->IsBoolean()) {
458 LOGI("Parse isModalValue");
459 properties.isModal = isModalValue->ToBoolean();
460 }
461
462 auto backgroundColorValue = obj->GetProperty("backgroundColor");
463 Color backgroundColor;
464 if (JSViewAbstract::ParseJsColor(backgroundColorValue, backgroundColor)) {
465 properties.backgroundColor = backgroundColor;
466 }
467
468 auto backgroundBlurStyle = obj->GetProperty("backgroundBlurStyle");
469 if (backgroundBlurStyle->IsNumber()) {
470 auto blurStyle = backgroundBlurStyle->ToNumber<int32_t>();
471 if (blurStyle >= static_cast<int>(BlurStyle::NO_MATERIAL) &&
472 blurStyle <= static_cast<int>(BlurStyle::COMPONENT_ULTRA_THICK)) {
473 properties.backgroundBlurStyle = blurStyle;
474 }
475 }
476 // Parse transition.
477 properties.transitionEffect = ParseJsTransitionEffect(args);
478 JSViewAbstract::SetDialogProperties(obj, properties);
479 JSViewAbstract::SetDialogHoverModeProperties(obj, properties);
480 AlertDialogModel::GetInstance()->SetShowDialog(properties);
481 }
482 }
483
JSBind(BindingTarget globalObj)484 void JSAlertDialog::JSBind(BindingTarget globalObj)
485 {
486 JSClass<JSAlertDialog>::Declare("AlertDialog");
487 JSClass<JSAlertDialog>::StaticMethod("show", &JSAlertDialog::Show);
488
489 JSClass<JSAlertDialog>::InheritAndBind<JSViewAbstract>(globalObj);
490 }
491 } // namespace OHOS::Ace::Framework
492