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