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