• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "bridge/declarative_frontend/engine/jsi/jsi_types.h"
20 #include "core/common/ace_engine.h"
21 #include "core/common/container.h"
22 #include "core/components_ng/base/frame_node.h"
23 #include "core/pipeline_ng/pipeline_context.h"
24 #include "frameworks/bridge/common/utils/engine_helper.h"
25 #include "frameworks/bridge/declarative_frontend/view_stack_processor.h"
26 #include "frameworks/core/components_ng/base/view_stack_processor.h"
27 
28 namespace OHOS::Ace::Framework {
29 namespace {
30 
31 constexpr uint32_t DELAY_TIME_FOR_STACK = 100;
32 const std::vector<DialogAlignment> DIALOG_ALIGNMENT = { DialogAlignment::TOP, DialogAlignment::CENTER,
33     DialogAlignment::BOTTOM, DialogAlignment::DEFAULT, DialogAlignment::TOP_START, DialogAlignment::TOP_END,
34     DialogAlignment::CENTER_START, DialogAlignment::CENTER_END, DialogAlignment::BOTTOM_START,
35     DialogAlignment::BOTTOM_END };
36 constexpr int32_t DEFAULT_ANIMATION_DURATION = 200;
37 
38 } // namespace
39 
ConstructorCallback(const JSCallbackInfo & info)40 void JSCustomDialogController::ConstructorCallback(const JSCallbackInfo& info)
41 {
42     int argc = info.Length();
43     if (argc > 1 && !info[0]->IsUndefined() && info[0]->IsObject() && !info[1]->IsUndefined() && info[1]->IsObject()) {
44         JSRef<JSObject> constructorArg = JSRef<JSObject>::Cast(info[0]);
45         JSRef<JSObject> ownerObj = JSRef<JSObject>::Cast(info[1]);
46 
47         // check if owner object is set
48         JSView* ownerView = ownerObj->Unwrap<JSView>();
49         if (ownerView == nullptr) {
50             LOGE("JSCustomDialogController creation with invalid arguments. Missing \'ownerView\'");
51             return;
52         }
53 
54         auto instance = new JSCustomDialogController(ownerView);
55         instance->ownerView_ = ownerView;
56 
57         // Process builder function.
58         JSRef<JSVal> builderCallback = constructorArg->GetProperty("builder");
59         if (!builderCallback->IsUndefined() && builderCallback->IsFunction()) {
60             instance->jsBuilderFunction_ =
61                 AceType::MakeRefPtr<JsFunction>(ownerObj, JSRef<JSFunc>::Cast(builderCallback));
62         } else {
63             delete instance;
64             instance = nullptr;
65             LOGE("JSCustomDialogController invalid builder function argument");
66             return;
67         }
68 
69         // Process cancel function.
70         JSRef<JSVal> cancelCallback = constructorArg->GetProperty("cancel");
71         if (!cancelCallback->IsUndefined() && cancelCallback->IsFunction()) {
72             auto jsCancelFunction = AceType::MakeRefPtr<JsFunction>(ownerObj, JSRef<JSFunc>::Cast(cancelCallback));
73             instance->jsCancelFunction_ = jsCancelFunction;
74 
75             if (Container::IsCurrentUseNewPipeline()) {
76                 auto onCancel = [execCtx = info.GetExecutionContext(), func = std::move(jsCancelFunction)]() {
77                     JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
78                     ACE_SCORING_EVENT("onCancel");
79                     func->Execute();
80                 };
81                 instance->dialogProperties_.onCancel = onCancel;
82             }
83         }
84 
85         // Parses autoCancel.
86         JSRef<JSVal> autoCancelValue = constructorArg->GetProperty("autoCancel");
87         if (autoCancelValue->IsBoolean()) {
88             instance->dialogProperties_.autoCancel = autoCancelValue->ToBoolean();
89         }
90 
91         // Parses customStyle.
92         JSRef<JSVal> customStyleValue = constructorArg->GetProperty("customStyle");
93         if (customStyleValue->IsBoolean()) {
94             instance->dialogProperties_.customStyle = customStyleValue->ToBoolean();
95         }
96 
97         // Parse alignment
98         auto alignmentValue = constructorArg->GetProperty("alignment");
99         if (alignmentValue->IsNumber()) {
100             auto alignment = alignmentValue->ToNumber<int32_t>();
101             if (alignment >= 0 && alignment <= static_cast<int32_t>(DIALOG_ALIGNMENT.size())) {
102                 instance->dialogProperties_.alignment = DIALOG_ALIGNMENT[alignment];
103             }
104         }
105 
106         // Parse offset
107         auto offsetValue = constructorArg->GetProperty("offset");
108         if (offsetValue->IsObject()) {
109             auto offsetObj = JSRef<JSObject>::Cast(offsetValue);
110             Dimension dx;
111             auto dxValue = offsetObj->GetProperty("dx");
112             JSViewAbstract::ParseJsDimensionVp(dxValue, dx);
113             Dimension dy;
114             auto dyValue = offsetObj->GetProperty("dy");
115             JSViewAbstract::ParseJsDimensionVp(dyValue, dy);
116             dx.ResetInvalidValue();
117             dy.ResetInvalidValue();
118             instance->dialogProperties_.offset = DimensionOffset(dx, dy);
119         }
120 
121         // Parses gridCount.
122         auto gridCountValue = constructorArg->GetProperty("gridCount");
123         if (gridCountValue->IsNumber()) {
124             instance->dialogProperties_.gridCount = gridCountValue->ToNumber<int32_t>();
125         }
126 
127         // Parse maskColor.
128         auto maskColorValue = constructorArg->GetProperty("maskColor");
129         Color maskColor;
130         if (JSViewAbstract::ParseJsColor(maskColorValue, maskColor)) {
131             instance->dialogProperties_.maskColor = maskColor;
132         }
133 
134         auto execContext = info.GetExecutionContext();
135         // Parse openAnimation.
136         auto openAnimationValue = constructorArg->GetProperty("openAnimation");
137         AnimationOption openAnimation;
138         if (ParseAnimation(execContext, openAnimationValue, openAnimation)) {
139             instance->dialogProperties_.openAnimation = openAnimation;
140         }
141 
142         // Parse closeAnimation.
143         auto closeAnimationValue = constructorArg->GetProperty("closeAnimation");
144         AnimationOption closeAnimation;
145         if (ParseAnimation(execContext, closeAnimationValue, closeAnimation)) {
146             instance->dialogProperties_.closeAnimation = closeAnimation;
147         }
148 
149         auto showInSubWindowValue = constructorArg->GetProperty("showInSubWindow");
150         if (showInSubWindowValue->IsBoolean()) {
151 #if defined(PREVIEW)
152             LOGW("[Engine Log] Unable to use the SubWindow in the Previewer. Perform this operation on the "
153                  "emulator or a real device instead.");
154 #else
155             instance->dialogProperties_.isShowInSubWindow = showInSubWindowValue->ToBoolean();
156 #endif
157         }
158 
159         info.SetReturnValue(instance);
160     } else {
161         LOGE("JSView creation with invalid arguments.");
162     }
163 }
164 
DestructorCallback(JSCustomDialogController * controller)165 void JSCustomDialogController::DestructorCallback(JSCustomDialogController* controller)
166 {
167     if (controller != nullptr) {
168         controller->ownerView_ = nullptr;
169         delete controller;
170     }
171 }
172 
NotifyDialogOperation(DialogOperation operation)173 void JSCustomDialogController::NotifyDialogOperation(DialogOperation operation)
174 {
175     LOGI("JSCustomDialogController(NotifyDialogOperation) operation: %{public}d", operation);
176     if (operation == DialogOperation::DIALOG_OPEN) {
177         isShown_ = true;
178         pending_ = false;
179         for (auto iter = dialogOperation_.begin(); iter != dialogOperation_.end();) {
180             if (*iter == DialogOperation::DIALOG_OPEN) {
181                 dialogOperation_.erase(iter++);
182                 continue;
183             }
184 
185             if (*iter == DialogOperation::DIALOG_CLOSE) {
186                 dialogOperation_.erase(iter);
187                 CloseDialog();
188                 break;
189             }
190         }
191     } else if (operation == DialogOperation::DIALOG_CLOSE) {
192         isShown_ = false;
193         pending_ = false;
194         for (auto iter = dialogOperation_.begin(); iter != dialogOperation_.end();) {
195             if (*iter == DialogOperation::DIALOG_CLOSE) {
196                 dialogOperation_.erase(iter++);
197                 continue;
198             }
199 
200             if (*iter == DialogOperation::DIALOG_OPEN) {
201                 dialogOperation_.erase(iter);
202                 ShowDialog();
203                 break;
204             }
205         }
206     }
207 }
208 
ShowDialog()209 void JSCustomDialogController::ShowDialog()
210 {
211     LOGI("JSCustomDialogController(ShowDialog)");
212     RefPtr<Container> container;
213     auto current = Container::Current();
214     if (!current) {
215         LOGE("Container is null.");
216         return;
217     }
218     if (current->IsSubContainer()) {
219         auto parentContainerId = SubwindowManager::GetInstance()->GetParentContainerId(Container::CurrentId());
220         container = AceEngine::Get().GetContainer(parentContainerId);
221     } else {
222         container = std::move(current);
223     }
224     if (!container) {
225         LOGE("Container is null.");
226         return;
227     }
228     auto context = AceType::DynamicCast<PipelineContext>(container->GetPipelineContext());
229     if (!context) {
230         LOGE("JSCustomDialogController No Context");
231         return;
232     }
233     dialogProperties_.customComponent = customDialog_;
234     EventMarker cancelMarker([cancelCallback = jsCancelFunction_]() {
235         if (cancelCallback) {
236             ACE_SCORING_EVENT("CustomDialog.cancel");
237             cancelCallback->Execute();
238         }
239     });
240     dialogProperties_.callbacks.try_emplace("cancel", cancelMarker);
241     dialogProperties_.onStatusChanged = [this](bool isShown) {
242         if (!isShown) {
243             this->isShown_ = isShown;
244         }
245     };
246 
247     auto executor = context->GetTaskExecutor();
248     if (!executor) {
249         LOGE("JSCustomDialogController(ShowDialog) No Executor. Cannot post task.");
250         return;
251     }
252 
253     if (pending_) {
254         LOGI("JSCustomDialogController(ShowDialog) current state is pending.");
255         dialogOperation_.emplace_back(DialogOperation::DIALOG_OPEN);
256         return;
257     }
258 
259     if (isShown_) {
260         LOGI("JSCustomDialogController(ShowDialog) CustomDialog has already shown.");
261         return;
262     }
263 
264     pending_ = true;
265     auto task = [context, dialogProperties = dialogProperties_, this]() mutable {
266         if (context) {
267             this->dialogComponent_ = context->ShowDialog(dialogProperties, false, "CustomDialog");
268         } else {
269             LOGE("JSCustomDialogController(ShowDialog) context is null.");
270         }
271         this->NotifyDialogOperation(DialogOperation::DIALOG_OPEN);
272     };
273     auto stack = context->GetLastStack();
274     auto result = false;
275     if (stack) {
276         result = executor->PostTask(task, TaskExecutor::TaskType::UI);
277     } else {
278         LOGE("JSCustomDialogController(ShowDialog) stack is null, post delay task.");
279         result = executor->PostDelayedTask(task, TaskExecutor::TaskType::UI, DELAY_TIME_FOR_STACK);
280     }
281     if (!result) {
282         LOGW("JSCustomDialogController(ShowDialog) fail to post task, reset pending status");
283         pending_ = false;
284     }
285 }
286 
CloseDialog()287 void JSCustomDialogController::CloseDialog()
288 {
289     LOGI("JSCustomDialogController(CloseDialog)");
290     RefPtr<Container> container;
291     auto current = Container::Current();
292     if (!current) {
293         LOGE("Container is null.");
294         return;
295     }
296     if (current->IsSubContainer()) {
297         auto parentContainerId = SubwindowManager::GetInstance()->GetParentContainerId(Container::CurrentId());
298         container = AceEngine::Get().GetContainer(parentContainerId);
299     } else {
300         container = std::move(current);
301     }
302     if (!container) {
303         LOGE("Container is null.");
304         return;
305     }
306     auto context = AceType::DynamicCast<PipelineContext>(container->GetPipelineContext());
307     if (!context) {
308         LOGE("JSCustomDialogController No Context");
309         return;
310     }
311     const auto& lastStack = context->GetLastStack();
312     if (!lastStack) {
313         LOGE("JSCustomDialogController No Stack!");
314         return;
315     }
316     auto executor = context->GetTaskExecutor();
317     if (!executor) {
318         LOGE("JSCustomDialogController(CloseDialog) No Executor. Cannot post task.");
319         return;
320     }
321 
322     if (pending_) {
323         LOGI("JSCustomDialogController(CloseDialog) current state is pending.");
324         dialogOperation_.emplace_back(DialogOperation::DIALOG_CLOSE);
325         return;
326     }
327 
328     pending_ = true;
329     auto task = [lastStack, dialogComponent = dialogComponent_, this]() {
330         if (!lastStack || !dialogComponent) {
331             LOGI("JSCustomDialogController(CloseDialog) stack or dialog is null.");
332             this->NotifyDialogOperation(DialogOperation::DIALOG_CLOSE);
333             return;
334         }
335         auto animator = dialogComponent->GetAnimator();
336         auto dialogId = dialogComponent->GetDialogId();
337         if (animator) {
338             if (!dialogComponent->HasStopListenerAdded()) {
339                 animator->AddStopListener([lastStack, dialogId] {
340                     if (lastStack) {
341                         lastStack->PopDialog(dialogId);
342                     }
343                 });
344                 dialogComponent->SetHasStopListenerAdded(true);
345             }
346             animator->Play();
347         } else {
348             lastStack->PopDialog(dialogId);
349         }
350         this->NotifyDialogOperation(DialogOperation::DIALOG_CLOSE);
351     };
352     auto result = executor->PostTask(task, TaskExecutor::TaskType::UI);
353     if (!result) {
354         LOGW("JSCustomDialogController(CloseDialog) fail to post task, reset pending status");
355         pending_ = false;
356     }
357 
358     dialogComponent_ = nullptr;
359 }
360 
JsOpenDialog(const JSCallbackInfo & info)361 void JSCustomDialogController::JsOpenDialog(const JSCallbackInfo& info)
362 {
363     LOGI("JSCustomDialogController(JsOpenDialog)");
364     if (!jsBuilderFunction_) {
365         LOGE("Builder of CustomDialog is null.");
366         return;
367     }
368     auto scopedDelegate = EngineHelper::GetCurrentDelegate();
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     // NG
376     if (Container::IsCurrentUseNewPipeline()) {
377         auto container = Container::Current();
378         auto currentId = Container::CurrentId();
379         CHECK_NULL_VOID(container);
380         if (container->IsSubContainer()) {
381             currentId = SubwindowManager::GetInstance()->GetParentContainerId(Container::CurrentId());
382             container = AceEngine::Get().GetContainer(currentId);
383         }
384         ContainerScope scope(currentId);
385         auto pipelineContext = container->GetPipelineContext();
386         CHECK_NULL_VOID(pipelineContext);
387         auto context = AceType::DynamicCast<NG::PipelineContext>(pipelineContext);
388         CHECK_NULL_VOID(context);
389         auto overlayManager = context->GetOverlayManager();
390         CHECK_NULL_VOID(overlayManager);
391 
392         NG::ScopedViewStackProcessor builderViewStackProcessor;
393         LOGD("custom JS node building");
394         jsBuilderFunction_->Execute();
395         auto customNode = NG::ViewStackProcessor::GetInstance()->Finish();
396         CHECK_NULL_VOID(customNode);
397 
398         dialogProperties_.onStatusChanged = [this](bool isShown) {
399             if (!isShown) {
400                 this->isShown_ = isShown;
401             }
402         };
403 
404         WeakPtr<NG::FrameNode> dialog;
405         if (dialogProperties_.isShowInSubWindow) {
406             dialog = SubwindowManager::GetInstance()->ShowDialogNG(dialogProperties_, customNode);
407         } else {
408             dialog = overlayManager->ShowDialog(dialogProperties_, customNode, false);
409         }
410         dialogs_.emplace_back(dialog);
411         return;
412     }
413 
414     // Cannot reuse component because might depend on state
415     if (customDialog_) {
416         customDialog_ = nullptr;
417     }
418 
419     {
420         ACE_SCORING_EVENT("CustomDialog.builder");
421         jsBuilderFunction_->Execute();
422     }
423     customDialog_ = ViewStackProcessor::GetInstance()->Finish();
424 
425     if (!customDialog_) {
426         LOGE("Builder does not generate view.");
427         return;
428     }
429 
430     ShowDialog();
431 }
432 
JsCloseDialog(const JSCallbackInfo & info)433 void JSCustomDialogController::JsCloseDialog(const JSCallbackInfo& info)
434 {
435     LOGI("JSCustomDialogController(JsCloseDialog)");
436     auto scopedDelegate = EngineHelper::GetCurrentDelegate();
437     if (!scopedDelegate) {
438         // this case usually means there is no foreground container, need to figure out the reason.
439         LOGE("scopedDelegate is null, please check");
440         return;
441     }
442 
443     if (Container::IsCurrentUseNewPipeline()) {
444         RefPtr<NG::FrameNode> dialog;
445         while (!dialogs_.empty()) {
446             dialog = dialogs_.back().Upgrade();
447             if (dialog) {
448                 // get the dialog not removed currently
449                 break;
450             }
451             dialogs_.pop_back();
452         }
453 
454         if (dialogs_.empty()) {
455             LOGW("dialogs are empty");
456             return;
457         }
458         if (!dialog) {
459             LOGW("dialog is null");
460             return;
461         }
462         auto container = Container::Current();
463         auto currentId = Container::CurrentId();
464         CHECK_NULL_VOID(container);
465         if (container->IsSubContainer() && !dialogProperties_.isShowInSubWindow) {
466             currentId = SubwindowManager::GetInstance()->GetParentContainerId(Container::CurrentId());
467             container = AceEngine::Get().GetContainer(currentId);
468         }
469         ContainerScope scope(currentId);
470         auto pipelineContext = container->GetPipelineContext();
471         CHECK_NULL_VOID(pipelineContext);
472         auto context = AceType::DynamicCast<NG::PipelineContext>(pipelineContext);
473         CHECK_NULL_VOID(context);
474         auto overlayManager = context->GetOverlayManager();
475         CHECK_NULL_VOID(overlayManager);
476         overlayManager->CloseDialog(dialog);
477         dialogs_.pop_back();
478         return;
479     }
480     CloseDialog();
481 }
482 
ParseAnimation(const JsiExecutionContext & execContext,const JsiRef<JsiValue> & animationValue,AnimationOption & result)483 bool JSCustomDialogController::ParseAnimation(
484     const JsiExecutionContext& execContext, const JsiRef<JsiValue>& animationValue, AnimationOption& result)
485 {
486     if (animationValue->IsNull() || !animationValue->IsObject()) {
487         return false;
488     }
489     auto animationArgs = JsonUtil::ParseJsonString(animationValue->ToString());
490     if (animationArgs->IsNull()) {
491         return false;
492     }
493     // If the attribute does not exist, the default value is used.
494     auto duration = animationArgs->GetInt("duration", DEFAULT_ANIMATION_DURATION);
495     auto delay = animationArgs->GetInt("delay", 0);
496     auto iterations = animationArgs->GetInt("iterations", 1);
497     auto tempo = static_cast<float>(animationArgs->GetDouble("tempo", 1.0));
498     auto direction = StringToAnimationDirection(animationArgs->GetString("playMode", "normal"));
499     RefPtr<Curve> curve;
500     auto curveArgs = animationArgs->GetValue("curve");
501     if (curveArgs->IsString()) {
502         curve = CreateCurve(animationArgs->GetString("curve", "linear"));
503     } else if (curveArgs->IsObject()) {
504         auto curveString = curveArgs->GetValue("__curveString");
505         if (!curveString) {
506             // Default AnimationOption which is invalid.
507             return false;
508         }
509         curve = CreateCurve(curveString->GetString());
510     } else {
511         curve = Curves::EASE_IN_OUT;
512     }
513     result.SetDuration(duration);
514     result.SetDelay(delay);
515     result.SetIteration(iterations);
516     result.SetTempo(tempo);
517     result.SetAnimationDirection(direction);
518     result.SetCurve(curve);
519 
520     JSRef<JSObject> obj = JSRef<JSObject>::Cast(animationValue);
521     JSRef<JSVal> onFinish = obj->GetProperty("onFinish");
522     std::function<void()> onFinishEvent;
523     if (onFinish->IsFunction()) {
524         RefPtr<JsFunction> jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onFinish));
525         onFinishEvent = [execCtx = execContext, func = std::move(jsFunc)]() {
526             JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
527             ACE_SCORING_EVENT("CustomDialog.onFinish");
528             func->Execute();
529         };
530         result.SetOnFinishEvent(onFinishEvent);
531     }
532     return true;
533 }
534 
JSBind(BindingTarget object)535 void JSCustomDialogController::JSBind(BindingTarget object)
536 {
537     JSClass<JSCustomDialogController>::Declare("CustomDialogController");
538     JSClass<JSCustomDialogController>::CustomMethod("open", &JSCustomDialogController::JsOpenDialog);
539     JSClass<JSCustomDialogController>::CustomMethod("close", &JSCustomDialogController::JsCloseDialog);
540     JSClass<JSCustomDialogController>::Bind(
541         object, &JSCustomDialogController::ConstructorCallback, &JSCustomDialogController::DestructorCallback);
542 }
543 
544 } // namespace OHOS::Ace::Framework
545