1 /*
2 * Copyright (c) 2021 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/js_view_context.h"
17
18 #include <functional>
19
20 #include "base/utils/system_properties.h"
21 #include "bridge/common/utils/engine_helper.h"
22 #include "bridge/common/utils/utils.h"
23 #include "bridge/declarative_frontend/engine/functions/js_function.h"
24 #include "bridge/declarative_frontend/view_stack_processor.h"
25 #include "core/common/ace_engine.h"
26 #include "core/common/container_scope.h"
27 #include "core/components/common/properties/animation_option.h"
28 #include "core/components_ng/base/view_stack_processor.h"
29
30 #ifdef USE_V8_ENGINE
31 #include "bridge/declarative_frontend/engine/v8/v8_declarative_engine.h"
32 #elif USE_QUICKJS_ENGINE
33 #include "bridge/declarative_frontend/engine/quickjs/qjs_declarative_engine_instance.h"
34 #elif USE_ARK_ENGINE
35 #include "bridge/declarative_frontend/engine/jsi/jsi_declarative_engine.h"
36 #endif
37
38 namespace OHOS::Ace::Framework {
39 namespace {
40
41 constexpr uint32_t DEFAULT_DURATION = 1000; // ms
42
AnimateToForStageMode(const RefPtr<PipelineBase> & pipelineContext,AnimationOption & option,const JSCallbackInfo & info,std::function<void ()> & onFinishEvent)43 void AnimateToForStageMode(const RefPtr<PipelineBase>& pipelineContext, AnimationOption& option,
44 const JSCallbackInfo& info, std::function<void()>& onFinishEvent)
45 {
46 auto triggerId = Container::CurrentId();
47 AceEngine::Get().NotifyContainers([triggerId, option](const RefPtr<Container>& container) {
48 auto context = container->GetPipelineContext();
49 if (!context) {
50 // pa container do not have pipeline context.
51 return;
52 }
53 if (!container->GetSettings().usingSharedRuntime) {
54 return;
55 }
56 auto frontendType = context->GetFrontendType();
57 if (frontendType != FrontendType::DECLARATIVE_JS && frontendType != FrontendType::JS_PLUGIN) {
58 LOGW("Not compatible frontType(%{public}d) for declarative. containerId: %{public}d", frontendType,
59 container->GetInstanceId());
60 }
61 ContainerScope scope(container->GetInstanceId());
62 context->FlushBuild();
63 if (context->GetInstanceId() == triggerId) {
64 return;
65 }
66 context->PrepareOpenImplicitAnimation();
67 });
68 pipelineContext->OpenImplicitAnimation(option, option.GetCurve(), onFinishEvent);
69 if (!Container::IsCurrentUseNewPipeline()) {
70 pipelineContext->SetSyncAnimationOption(option);
71 }
72 // Execute the function.
73 JSRef<JSFunc> jsAnimateToFunc = JSRef<JSFunc>::Cast(info[1]);
74 jsAnimateToFunc->Call(info[1]);
75 AceEngine::Get().NotifyContainers([triggerId](const RefPtr<Container>& container) {
76 auto context = container->GetPipelineContext();
77 if (!context) {
78 // pa container do not have pipeline context.
79 return;
80 }
81 if (!container->GetSettings().usingSharedRuntime) {
82 return;
83 }
84 auto frontendType = context->GetFrontendType();
85 if (frontendType != FrontendType::DECLARATIVE_JS && frontendType != FrontendType::JS_PLUGIN) {
86 LOGW("Not compatible frontType(%{public}d) for declarative. containerId: %{public}d", frontendType,
87 container->GetInstanceId());
88 }
89 ContainerScope scope(container->GetInstanceId());
90 context->FlushBuild();
91 if (context->GetInstanceId() == triggerId) {
92 return;
93 }
94 context->PrepareCloseImplicitAnimation();
95 });
96 if (!Container::IsCurrentUseNewPipeline()) {
97 pipelineContext->SetSyncAnimationOption(AnimationOption());
98 }
99 pipelineContext->CloseImplicitAnimation();
100 }
101
AnimateToForFaMode(const RefPtr<PipelineBase> & pipelineContext,AnimationOption & option,const JSCallbackInfo & info,std::function<void ()> & onFinishEvent)102 void AnimateToForFaMode(const RefPtr<PipelineBase>& pipelineContext, AnimationOption& option,
103 const JSCallbackInfo& info, std::function<void()>& onFinishEvent)
104 {
105 pipelineContext->FlushBuild();
106 pipelineContext->OpenImplicitAnimation(option, option.GetCurve(), onFinishEvent);
107 if (!Container::IsCurrentUseNewPipeline()) {
108 pipelineContext->SetSyncAnimationOption(option);
109 }
110 JSRef<JSFunc> jsAnimateToFunc = JSRef<JSFunc>::Cast(info[1]);
111 jsAnimateToFunc->Call(info[1]);
112 pipelineContext->FlushBuild();
113 if (!Container::IsCurrentUseNewPipeline()) {
114 pipelineContext->SetSyncAnimationOption(AnimationOption());
115 }
116 pipelineContext->CloseImplicitAnimation();
117 }
118
119 } // namespace
120
CreateAnimation(const std::unique_ptr<JsonValue> & animationArgs,bool isForm)121 const AnimationOption JSViewContext::CreateAnimation(const std::unique_ptr<JsonValue>& animationArgs, bool isForm)
122 {
123 AnimationOption option = AnimationOption();
124 if (!animationArgs) {
125 LOGW("CreateAnimation: animationArgs is null");
126 return option;
127 }
128 // If the attribute does not exist, the default value is used.
129 auto duration = animationArgs->GetInt("duration", DEFAULT_DURATION);
130 auto delay = animationArgs->GetInt("delay", 0);
131 auto iterations = animationArgs->GetInt("iterations", 1);
132 auto tempo = animationArgs->GetDouble("tempo", 1.0);
133 auto direction = StringToAnimationDirection(animationArgs->GetString("playMode", "normal"));
134 RefPtr<Curve> curve;
135 auto curveArgs = animationArgs->GetValue("curve");
136 if (curveArgs->IsString()) {
137 curve = CreateCurve(animationArgs->GetString("curve", "linear"));
138 } else if (curveArgs->IsObject()) {
139 auto curveString = curveArgs->GetValue("__curveString");
140 if (!curveString) {
141 // Default AnimationOption which is invalid.
142 return option;
143 }
144 curve = CreateCurve(curveString->GetString());
145 } else {
146 curve = Curves::EASE_IN_OUT;
147 }
148
149 // limit animation for ArkTS Form
150 if (isForm) {
151 duration = std::min(duration, static_cast<int32_t>(DEFAULT_DURATION));
152 delay = 0;
153 iterations = 1;
154 tempo = 1.0;
155 }
156
157 option.SetDuration(duration);
158 option.SetDelay(delay);
159 option.SetIteration(iterations);
160 option.SetTempo(tempo);
161 option.SetAnimationDirection(direction);
162 option.SetCurve(curve);
163 return option;
164 }
165
JSAnimation(const JSCallbackInfo & info)166 void JSViewContext::JSAnimation(const JSCallbackInfo& info)
167 {
168 LOGD("JSAnimation");
169 auto scopedDelegate = EngineHelper::GetCurrentDelegate();
170 if (!scopedDelegate) {
171 // this case usually means there is no foreground container, need to figure out the reason.
172 LOGE("scopedDelegate is null, please check");
173 return;
174 }
175 if (info.Length() < 1) {
176 LOGE("The arg is wrong, it is supposed to have 1 object argument.");
177 return;
178 }
179 AnimationOption option = AnimationOption();
180 auto container = Container::Current();
181 CHECK_NULL_VOID(container);
182 auto pipelineContextBase = container->GetPipelineContext();
183 CHECK_NULL_VOID(pipelineContextBase);
184 if (info[0]->IsNull() || !info[0]->IsObject()) {
185 if (Container::IsCurrentUseNewPipeline()) {
186 NG::ViewStackProcessor::GetInstance()->FlushImplicitAnimation();
187 pipelineContextBase->CloseImplicitAnimation();
188 } else {
189 LOGE("JSAnimation: info[0] is null or not object.");
190 ViewStackProcessor::GetInstance()->SetImplicitAnimationOption(option);
191 }
192 return;
193 }
194 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
195 JSRef<JSVal> onFinish = obj->GetProperty("onFinish");
196 std::function<void()> onFinishEvent;
197 if (onFinish->IsFunction()) {
198 RefPtr<JsFunction> jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onFinish));
199 onFinishEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc),
200 id = Container::CurrentId()]() {
201 ContainerScope scope(id);
202 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
203 func->Execute();
204 };
205 }
206 auto animationArgs = JsonUtil::ParseJsonString(info[0]->ToString());
207 if (animationArgs->IsNull()) {
208 LOGE("Js Parse failed. animationArgs is null.");
209 if (Container::IsCurrentUseNewPipeline()) {
210 pipelineContextBase->CloseImplicitAnimation();
211 } else {
212 ViewStackProcessor::GetInstance()->SetImplicitAnimationOption(option);
213 }
214 return;
215 }
216 option = CreateAnimation(animationArgs, pipelineContextBase->IsFormRender());
217 option.SetOnFinishEvent(onFinishEvent);
218 if (SystemProperties::GetRosenBackendEnabled()) {
219 option.SetAllowRunningAsynchronously(true);
220 }
221 if (Container::IsCurrentUseNewPipeline()) {
222 pipelineContextBase->OpenImplicitAnimation(option, option.GetCurve(), onFinishEvent);
223 } else {
224 ViewStackProcessor::GetInstance()->SetImplicitAnimationOption(option);
225 }
226 }
227
JSAnimateTo(const JSCallbackInfo & info)228 void JSViewContext::JSAnimateTo(const JSCallbackInfo& info)
229 {
230 auto scopedDelegate = EngineHelper::GetCurrentDelegate();
231 if (!scopedDelegate) {
232 // this case usually means there is no foreground container, need to figure out the reason.
233 LOGE("scopedDelegate is null, please check");
234 return;
235 }
236 if (info.Length() < 2) {
237 LOGE("The arg is wrong, it is supposed to have two arguments.");
238 return;
239 }
240 if (!info[0]->IsObject()) {
241 LOGE("1st argument is not object.");
242 return;
243 }
244 // 2nd argument should be a closure passed to the animateTo function.
245 if (!info[1]->IsFunction()) {
246 LOGE("2nd argument is not a function.");
247 return;
248 }
249
250 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
251 JSRef<JSVal> onFinish = obj->GetProperty("onFinish");
252 std::function<void()> onFinishEvent;
253 if (onFinish->IsFunction()) {
254 RefPtr<JsFunction> jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onFinish));
255 onFinishEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc),
256 id = Container::CurrentId()]() {
257 ContainerScope scope(id);
258 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
259 func->Execute();
260 };
261 }
262
263 auto animationArgs = JsonUtil::ParseJsonString(info[0]->ToString());
264 if (animationArgs->IsNull()) {
265 LOGE("Js Parse failed. animationArgs is null.");
266 return;
267 }
268
269 auto container = Container::Current();
270 CHECK_NULL_VOID(container);
271 auto pipelineContext = container->GetPipelineContext();
272 CHECK_NULL_VOID(pipelineContext);
273
274 AnimationOption option = CreateAnimation(animationArgs, pipelineContext->IsFormRender());
275 if (SystemProperties::GetRosenBackendEnabled()) {
276 bool usingSharedRuntime = container->GetSettings().usingSharedRuntime;
277 LOGD("RSAnimationInfo: Begin JSAnimateTo, usingSharedRuntime: %{public}d", usingSharedRuntime);
278 if (usingSharedRuntime) {
279 AnimateToForStageMode(pipelineContext, option, info, onFinishEvent);
280 } else {
281 AnimateToForFaMode(pipelineContext, option, info, onFinishEvent);
282 }
283 LOGD("RSAnimationInfo: End JSAnimateTo");
284 } else {
285 pipelineContext->FlushBuild();
286 pipelineContext->SaveExplicitAnimationOption(option);
287 // Execute the function.
288 JSRef<JSFunc> jsAnimateToFunc = JSRef<JSFunc>::Cast(info[1]);
289 jsAnimateToFunc->Call(info[1]);
290 pipelineContext->FlushBuild();
291 pipelineContext->CreateExplicitAnimator(onFinishEvent);
292 pipelineContext->ClearExplicitAnimationOption();
293 }
294 }
295
JSBind(BindingTarget globalObj)296 void JSViewContext::JSBind(BindingTarget globalObj)
297 {
298 JSClass<JSViewContext>::Declare("Context");
299 JSClass<JSViewContext>::StaticMethod("animation", JSAnimation);
300 JSClass<JSViewContext>::StaticMethod("animateTo", JSAnimateTo);
301 JSClass<JSViewContext>::Bind<>(globalObj);
302 }
303
304 } // namespace OHOS::Ace::Framework
305