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