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