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 
16 #include "bridge/declarative_frontend/jsview/dialog/js_custom_dialog_controller.h"
17 
18 #include "base/subwindow/subwindow_manager.h"
19 #include "base/utils/system_properties.h"
20 #include "base/utils/utils.h"
21 #include "bridge/declarative_frontend/engine/jsi/jsi_types.h"
22 #include "bridge/declarative_frontend/jsview/models/custom_dialog_controller_model_impl.h"
23 #include "core/common/ace_engine.h"
24 #include "core/common/container.h"
25 #include "core/components_ng/base/view_stack_processor.h"
26 #include "core/components_ng/pattern/dialog/custom_dialog_controller_model_ng.h"
27 #include "core/pipeline_ng/pipeline_context.h"
28 #include "frameworks/bridge/common/utils/engine_helper.h"
29 #include "frameworks/bridge/declarative_frontend/jsview/js_view_abstract.h"
30 
31 namespace OHOS::Ace {
32 std::unique_ptr<CustomDialogControllerModel> CustomDialogControllerModel::instance_ = nullptr;
33 std::mutex CustomDialogControllerModel::mutex_;
GetInstance()34 CustomDialogControllerModel* CustomDialogControllerModel::GetInstance()
35 {
36     if (!instance_) {
37         std::lock_guard<std::mutex> lock(mutex_);
38         if (!instance_) {
39 #ifdef NG_BUILD
40             instance_.reset(new NG::CustomDialogControllerModelNG());
41 #else
42             if (Container::IsCurrentUseNewPipeline()) {
43                 instance_.reset(new NG::CustomDialogControllerModelNG());
44             } else {
45                 instance_.reset(new Framework::CustomDialogControllerModelImpl());
46             }
47 #endif
48         }
49     }
50     return instance_.get();
51 }
52 } // namespace OHOS::Ace
53 
54 namespace OHOS::Ace::Framework {
55 namespace {
56 const std::vector<DialogAlignment> DIALOG_ALIGNMENT = { DialogAlignment::TOP, DialogAlignment::CENTER,
57     DialogAlignment::BOTTOM, DialogAlignment::DEFAULT, DialogAlignment::TOP_START, DialogAlignment::TOP_END,
58     DialogAlignment::CENTER_START, DialogAlignment::CENTER_END, DialogAlignment::BOTTOM_START,
59     DialogAlignment::BOTTOM_END };
60 const std::vector<KeyboardAvoidMode> KEYBOARD_AVOID_MODE = { KeyboardAvoidMode::DEFAULT, KeyboardAvoidMode::NONE };
61 constexpr int32_t DEFAULT_ANIMATION_DURATION = 200;
62 
63 } // namespace
64 
ConstructorCallback(const JSCallbackInfo & info)65 void JSCustomDialogController::ConstructorCallback(const JSCallbackInfo& info)
66 {
67     int argc = info.Length();
68     if (argc > 1 && !info[0]->IsUndefined() && info[0]->IsObject() && !info[1]->IsUndefined() && info[1]->IsObject()) {
69         JSRef<JSObject> constructorArg = JSRef<JSObject>::Cast(info[0]);
70         JSRef<JSObject> ownerObj = JSRef<JSObject>::Cast(info[1]);
71 
72         // check if owner object is set
73         JSView* ownerView = ownerObj->Unwrap<JSView>();
74         auto instance = AceType::MakeRefPtr<JSCustomDialogController>(ownerView);
75         if (ownerView == nullptr) {
76             instance->IncRefCount();
77             info.SetReturnValue(AceType::RawPtr(instance));
78             instance = nullptr;
79             return;
80         }
81 
82         // Process builder function.
83         JSRef<JSVal> builderCallback = constructorArg->GetProperty("builder");
84         if (!builderCallback->IsUndefined() && builderCallback->IsFunction()) {
85             instance->jsBuilderFunction_ =
86                 AceType::MakeRefPtr<JsWeakFunction>(ownerObj, JSRef<JSFunc>::Cast(builderCallback));
87         } else {
88             instance->jsBuilderFunction_ = nullptr;
89             instance->IncRefCount();
90             info.SetReturnValue(AceType::RawPtr(instance));
91             instance = nullptr;
92             return;
93         }
94 
95         // Process cancel function.
96         JSRef<JSVal> cancelCallback = constructorArg->GetProperty("cancel");
97         if (!cancelCallback->IsUndefined() && cancelCallback->IsFunction()) {
98             auto frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
99             auto jsCancelFunction = AceType::MakeRefPtr<JsWeakFunction>(ownerObj, JSRef<JSFunc>::Cast(cancelCallback));
100             instance->jsCancelFunction_ = jsCancelFunction;
101 
102             auto onCancel = [execCtx = info.GetExecutionContext(), func = std::move(jsCancelFunction),
103                                 node = frameNode]() {
104                 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
105                 ACE_SCORING_EVENT("onCancel");
106                 auto pipelineContext = PipelineContext::GetCurrentContext();
107                 CHECK_NULL_VOID(pipelineContext);
108                 pipelineContext->UpdateCurrentActiveNode(node);
109                 func->Execute();
110             };
111             instance->dialogProperties_.onCancel = onCancel;
112         }
113 
114         std::function<void(const int32_t& info)> onWillDismissFunc = nullptr;
115         JSViewAbstract::ParseDialogCallback(constructorArg, onWillDismissFunc);
116         instance->dialogProperties_.onWillDismiss = onWillDismissFunc;
117 
118         // Parses autoCancel.
119         JSRef<JSVal> autoCancelValue = constructorArg->GetProperty("autoCancel");
120         if (autoCancelValue->IsBoolean()) {
121             instance->dialogProperties_.autoCancel = autoCancelValue->ToBoolean();
122         }
123 
124         // Parses customStyle.
125         JSRef<JSVal> customStyleValue = constructorArg->GetProperty("customStyle");
126         if (customStyleValue->IsBoolean()) {
127             instance->dialogProperties_.customStyle = customStyleValue->ToBoolean();
128         }
129 
130         // Parse alignment
131         auto alignmentValue = constructorArg->GetProperty("alignment");
132         if (alignmentValue->IsNumber()) {
133             auto alignment = alignmentValue->ToNumber<int32_t>();
134             if (alignment >= 0 && alignment <= static_cast<int32_t>(DIALOG_ALIGNMENT.size())) {
135                 instance->dialogProperties_.alignment = DIALOG_ALIGNMENT[alignment];
136             }
137         }
138 
139         // Parse keyboardAvoidMode
140         auto avoidModeValue = constructorArg->GetProperty("keyboardAvoidMode");
141         if (avoidModeValue->IsNumber()) {
142             auto avoidMode = avoidModeValue->ToNumber<int32_t>();
143             if (avoidMode >= 0 && avoidMode < static_cast<int32_t>(KEYBOARD_AVOID_MODE.size())) {
144                 instance->dialogProperties_.keyboardAvoidMode = KEYBOARD_AVOID_MODE[avoidMode];
145             }
146         }
147 
148         // Parse offset
149         auto offsetValue = constructorArg->GetProperty("offset");
150         if (offsetValue->IsObject()) {
151             auto offsetObj = JSRef<JSObject>::Cast(offsetValue);
152             CalcDimension dx;
153             auto dxValue = offsetObj->GetProperty("dx");
154             JSViewAbstract::ParseJsDimensionVp(dxValue, dx);
155             CalcDimension dy;
156             auto dyValue = offsetObj->GetProperty("dy");
157             JSViewAbstract::ParseJsDimensionVp(dyValue, dy);
158             dx.ResetInvalidValue();
159             dy.ResetInvalidValue();
160             instance->dialogProperties_.offset = DimensionOffset(dx, dy);
161         }
162 
163         // Parses gridCount.
164         auto gridCountValue = constructorArg->GetProperty("gridCount");
165         if (gridCountValue->IsNumber()) {
166             instance->dialogProperties_.gridCount = gridCountValue->ToNumber<int32_t>();
167         }
168 
169         // Parse maskColor.
170         auto maskColorValue = constructorArg->GetProperty("maskColor");
171         Color maskColor;
172         if (JSViewAbstract::ParseJsColor(maskColorValue, maskColor)) {
173             instance->dialogProperties_.maskColor = maskColor;
174         }
175 
176         // Parse maskRect.
177         auto maskRectValue = constructorArg->GetProperty("maskRect");
178         DimensionRect maskRect;
179         if (JSViewAbstract::ParseJsDimensionRect(maskRectValue, maskRect)) {
180             instance->dialogProperties_.maskRect = maskRect;
181         }
182 
183         // Parse backgroundColor.
184         auto backgroundColorValue = constructorArg->GetProperty("backgroundColor");
185         Color backgroundColor;
186         if (JSViewAbstract::ParseJsColor(backgroundColorValue, backgroundColor)) {
187             instance->dialogProperties_.backgroundColor = backgroundColor;
188         }
189 
190         // Parse backgroundBlurStyle.
191         auto backgroundBlurStyle = constructorArg->GetProperty("backgroundBlurStyle");
192         if (backgroundBlurStyle->IsNumber()) {
193             auto blurStyle = backgroundBlurStyle->ToNumber<int32_t>();
194             if (blurStyle >= static_cast<int>(BlurStyle::NO_MATERIAL) &&
195                 blurStyle <= static_cast<int>(BlurStyle::COMPONENT_ULTRA_THICK)) {
196                 instance->dialogProperties_.backgroundBlurStyle = blurStyle;
197             }
198         }
199 
200         auto execContext = info.GetExecutionContext();
201         // Parse openAnimation.
202         auto openAnimationValue = constructorArg->GetProperty("openAnimation");
203         AnimationOption openAnimation;
204         if (ParseAnimation(execContext, openAnimationValue, openAnimation)) {
205             instance->dialogProperties_.openAnimation = openAnimation;
206         }
207 
208         // Parse closeAnimation.
209         auto closeAnimationValue = constructorArg->GetProperty("closeAnimation");
210         AnimationOption closeAnimation;
211         if (ParseAnimation(execContext, closeAnimationValue, closeAnimation)) {
212             instance->dialogProperties_.closeAnimation = closeAnimation;
213         }
214 
215         // Parse showInSubWindowValue.
216         auto showInSubWindowValue = constructorArg->GetProperty("showInSubWindow");
217         if (showInSubWindowValue->IsBoolean()) {
218 #if defined(PREVIEW)
219             LOGW("[Engine Log] Unable to use the SubWindow in the Previewer. Perform this operation on the "
220                  "emulator or a real device instead.");
221 #else
222             instance->dialogProperties_.isShowInSubWindow = showInSubWindowValue->ToBoolean();
223 #endif
224         }
225 
226         // Parse isModal.
227         auto isModalValue = constructorArg->GetProperty("isModal");
228         if (isModalValue->IsBoolean()) {
229             instance->dialogProperties_.isModal = isModalValue->ToBoolean();
230         }
231 
232         JSViewAbstract::SetDialogProperties(constructorArg, instance->dialogProperties_);
233         JSViewAbstract::SetDialogHoverModeProperties(constructorArg, instance->dialogProperties_);
234         instance->IncRefCount();
235         info.SetReturnValue(AceType::RawPtr(instance));
236     }
237 }
238 
DestructorCallback(JSCustomDialogController * controller)239 void JSCustomDialogController::DestructorCallback(JSCustomDialogController* controller)
240 {
241     if (controller != nullptr) {
242         controller->ownerView_ = nullptr;
243         controller->DecRefCount();
244     }
245 }
246 
JsOpenDialog(const JSCallbackInfo & info)247 void JSCustomDialogController::JsOpenDialog(const JSCallbackInfo& info)
248 {
249     if (!jsBuilderFunction_) {
250         return;
251     }
252 
253     if (this->ownerView_ == nullptr) {
254         return;
255     }
256     auto containerId = this->ownerView_->GetInstanceId();
257     ContainerScope containerScope(containerId);
258     WeakPtr<NG::FrameNode> frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
259     auto pipelineContext = PipelineContext::GetCurrentContext();
260     CHECK_NULL_VOID(pipelineContext);
261 
262     auto scopedDelegate = EngineHelper::GetCurrentDelegate();
263     if (!scopedDelegate) {
264         // this case usually means there is no foreground container, need to figure out the reason.
265         return;
266     }
267 
268     auto buildFunc = [buildfunc = jsBuilderFunction_, node = frameNode, context = pipelineContext]() {
269         {
270             ACE_SCORING_EVENT("CustomDialog.builder");
271             context->UpdateCurrentActiveNode(node);
272             buildfunc->Execute();
273         }
274     };
275 
276     auto cancelTask = ([cancelCallback = jsCancelFunction_, node = frameNode, context = pipelineContext]() {
277         if (cancelCallback) {
278             ACE_SCORING_EVENT("CustomDialog.cancel");
279             context->UpdateCurrentActiveNode(node);
280             cancelCallback->Execute();
281         }
282     });
283 
284     auto container = Container::Current();
285     if (container && container->IsScenceBoardWindow() && !dialogProperties_.windowScene.Upgrade()) {
286         dialogProperties_.isScenceBoardDialog = true;
287         auto viewNode = this->ownerView_->GetViewNode();
288         CHECK_NULL_VOID(viewNode);
289         auto parentCustom = AceType::DynamicCast<NG::CustomNode>(viewNode);
290         CHECK_NULL_VOID(parentCustom);
291         auto parent = parentCustom->GetParent();
292         while (parent && parent->GetTag() != V2::WINDOW_SCENE_ETS_TAG) {
293             parent = parent->GetParent();
294         }
295         if (parent) {
296             dialogProperties_.windowScene = parent;
297         }
298     }
299     dialogProperties_.isSysBlurStyle =
300         Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE) ? true : false;
301     CustomDialogControllerModel::GetInstance()->SetOpenDialog(dialogProperties_, WeakClaim(this), dialogs_, pending_,
302         isShown_, std::move(cancelTask), std::move(buildFunc), dialogComponent_, customDialog_, dialogOperation_);
303 }
304 
JsCloseDialog(const JSCallbackInfo & info)305 void JSCustomDialogController::JsCloseDialog(const JSCallbackInfo& info)
306 {
307 
308     if (this->ownerView_ == nullptr) {
309         return;
310     }
311     auto containerId = this->ownerView_->GetInstanceId();
312     ContainerScope containerScope(containerId);
313 
314     auto scopedDelegate = EngineHelper::GetCurrentDelegate();
315     if (!scopedDelegate) {
316         // this case usually means there is no foreground container, need to figure out the reason.
317         return;
318     }
319 
320     WeakPtr<NG::FrameNode> frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
321     auto cancelTask = ([cancelCallback = jsCancelFunction_, node = frameNode]() {
322         if (cancelCallback) {
323             ACE_SCORING_EVENT("CustomDialog.cancel");
324             auto pipelineContext = PipelineContext::GetCurrentContext();
325             CHECK_NULL_VOID(pipelineContext);
326             pipelineContext->UpdateCurrentActiveNode(node);
327             cancelCallback->Execute();
328         }
329     });
330 
331     CustomDialogControllerModel::GetInstance()->SetCloseDialog(dialogProperties_, WeakClaim(this), dialogs_, pending_,
332         isShown_, std::move(cancelTask), dialogComponent_, customDialog_, dialogOperation_);
333 }
334 
ParseAnimation(const JsiExecutionContext & execContext,const JsiRef<JsiValue> & animationValue,AnimationOption & result)335 bool JSCustomDialogController::ParseAnimation(
336     const JsiExecutionContext& execContext, const JsiRef<JsiValue>& animationValue, AnimationOption& result)
337 {
338     if (animationValue->IsNull() || !animationValue->IsObject()) {
339         return false;
340     }
341 
342     JSRef<JSObject> obj = JSRef<JSObject>::Cast(animationValue);
343     // If the attribute does not exist, the default value is used.
344     int32_t duration = obj->GetPropertyValue<int32_t>("duration", DEFAULT_ANIMATION_DURATION);
345     int32_t delay = obj->GetPropertyValue<int32_t>("delay", 0);
346     int32_t iterations = obj->GetPropertyValue<int32_t>("iterations", 1);
347     float tempo = obj->GetPropertyValue<float>("tempo", 1.0);
348     auto finishCallbackType = static_cast<FinishCallbackType>(obj->GetPropertyValue<int32_t>("finishCallbackType", 0));
349     if (NonPositive(tempo)) {
350         tempo = 1.0f;
351     }
352     auto direction = StringToAnimationDirection(obj->GetPropertyValue<std::string>("playMode", "normal"));
353     RefPtr<Curve> curve;
354     JSRef<JSVal> curveArgs = obj->GetProperty("curve");
355     if (curveArgs->IsString()) {
356         curve = CreateCurve(obj->GetPropertyValue<std::string>("curve", "linear"));
357     } else if (curveArgs->IsObject()) {
358         JSRef<JSObject> curveObj = JSRef<JSObject>::Cast(curveArgs);
359         JSRef<JSVal> curveString = curveObj->GetProperty("__curveString");
360         if (!curveString->IsString()) {
361             // Default AnimationOption which is invalid.
362             return false;
363         }
364         curve = CreateCurve(curveString->ToString());
365     } else {
366         curve = Curves::EASE_IN_OUT;
367     }
368     result.SetDuration(duration);
369     result.SetDelay(delay);
370     result.SetIteration(iterations);
371     result.SetTempo(tempo);
372     result.SetAnimationDirection(direction);
373     result.SetCurve(curve);
374     result.SetFinishCallbackType(finishCallbackType);
375 
376     JSRef<JSVal> onFinish = obj->GetProperty("onFinish");
377     std::function<void()> onFinishEvent;
378     if (onFinish->IsFunction()) {
379         auto frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
380         auto jsFunc = AceType::MakeRefPtr<JsWeakFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onFinish));
381         onFinishEvent = [execCtx = execContext, func = std::move(jsFunc), node = frameNode]() {
382             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
383             ACE_SCORING_EVENT("CustomDialog.onFinish");
384             auto pipelineContext = PipelineContext::GetCurrentContext();
385             CHECK_NULL_VOID(pipelineContext);
386             pipelineContext->UpdateCurrentActiveNode(node);
387             func->Execute();
388         };
389         result.SetOnFinishEvent(onFinishEvent);
390     }
391     return true;
392 }
393 
JSBind(BindingTarget object)394 void JSCustomDialogController::JSBind(BindingTarget object)
395 {
396     JSClass<JSCustomDialogController>::Declare("NativeCustomDialogController");
397     JSClass<JSCustomDialogController>::CustomMethod("open", &JSCustomDialogController::JsOpenDialog);
398     JSClass<JSCustomDialogController>::CustomMethod("close", &JSCustomDialogController::JsCloseDialog);
399     JSClass<JSCustomDialogController>::Bind(
400         object, &JSCustomDialogController::ConstructorCallback, &JSCustomDialogController::DestructorCallback);
401 }
402 } // namespace OHOS::Ace::Framework
403