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/log/jank_frame_report.h"
21 #include "base/utils/system_properties.h"
22 #include "bridge/common/utils/engine_helper.h"
23 #include "bridge/common/utils/utils.h"
24 #include "bridge/declarative_frontend/engine/functions/js_function.h"
25 #include "bridge/declarative_frontend/jsview/models/view_context_model_impl.h"
26 #include "core/common/ace_engine.h"
27 #include "core/components_ng/pattern/view_context/view_context_model_ng.h"
28
29 #ifdef USE_ARK_ENGINE
30 #include "bridge/declarative_frontend/engine/jsi/jsi_declarative_engine.h"
31 #endif
32
33 namespace OHOS::Ace {
34
35 std::unique_ptr<ViewContextModel> ViewContextModel::instance_ = nullptr;
36 std::mutex ViewContextModel::mutex_;
37
GetInstance()38 ViewContextModel* ViewContextModel::GetInstance()
39 {
40 if (!instance_) {
41 std::lock_guard<std::mutex> lock(mutex_);
42 if (!instance_) {
43 #ifdef NG_BUILD
44 instance_.reset(new NG::ViewContextModelNG());
45 #else
46 if (Container::IsCurrentUseNewPipeline()) {
47 instance_.reset(new NG::ViewContextModelNG());
48 } else {
49 instance_.reset(new Framework::ViewContextModelImpl());
50 }
51 #endif
52 }
53 }
54 return instance_.get();
55 }
56
57 } // namespace OHOS::Ace
58
59 namespace OHOS::Ace::Framework {
60 namespace {
61
62 constexpr uint32_t DEFAULT_DURATION = 1000; // ms
63
AnimateToForStageMode(const RefPtr<PipelineBase> & pipelineContext,AnimationOption & option,JSRef<JSFunc> jsAnimateToFunc,std::function<void ()> & onFinishEvent)64 void AnimateToForStageMode(const RefPtr<PipelineBase>& pipelineContext, AnimationOption& option,
65 JSRef<JSFunc> jsAnimateToFunc, std::function<void()>& onFinishEvent)
66 {
67 auto triggerId = Container::CurrentId();
68 AceEngine::Get().NotifyContainers([triggerId, option](const RefPtr<Container>& container) {
69 auto context = container->GetPipelineContext();
70 if (!context) {
71 // pa container do not have pipeline context.
72 return;
73 }
74 if (!container->GetSettings().usingSharedRuntime) {
75 return;
76 }
77 if (!container->IsFRSCardContainer() && !container->WindowIsShow()) {
78 return;
79 }
80 auto frontendType = context->GetFrontendType();
81 if (frontendType != FrontendType::DECLARATIVE_JS && frontendType != FrontendType::JS_PLUGIN) {
82 LOGW("Not compatible frontType(%{public}d) for declarative. containerId: %{public}d", frontendType,
83 container->GetInstanceId());
84 }
85 ContainerScope scope(container->GetInstanceId());
86 context->FlushBuild();
87 if (context->GetInstanceId() == triggerId) {
88 return;
89 }
90 context->PrepareOpenImplicitAnimation();
91 });
92 pipelineContext->OpenImplicitAnimation(option, option.GetCurve(), onFinishEvent);
93 pipelineContext->SetSyncAnimationOption(option);
94 // Execute the function.
95 jsAnimateToFunc->Call(jsAnimateToFunc);
96 AceEngine::Get().NotifyContainers([triggerId](const RefPtr<Container>& container) {
97 auto context = container->GetPipelineContext();
98 if (!context) {
99 // pa container do not have pipeline context.
100 return;
101 }
102 if (!container->GetSettings().usingSharedRuntime) {
103 return;
104 }
105 if (!container->IsFRSCardContainer() && !container->WindowIsShow()) {
106 return;
107 }
108 auto frontendType = context->GetFrontendType();
109 if (frontendType != FrontendType::DECLARATIVE_JS && frontendType != FrontendType::JS_PLUGIN) {
110 LOGW("Not compatible frontType(%{public}d) for declarative. containerId: %{public}d", frontendType,
111 container->GetInstanceId());
112 }
113 ContainerScope scope(container->GetInstanceId());
114 context->FlushBuild();
115 if (context->GetInstanceId() == triggerId) {
116 return;
117 }
118 context->PrepareCloseImplicitAnimation();
119 });
120 pipelineContext->SetSyncAnimationOption(AnimationOption());
121 pipelineContext->CloseImplicitAnimation();
122 }
123
AnimateToForFaMode(const RefPtr<PipelineBase> & pipelineContext,AnimationOption & option,const JSCallbackInfo & info,std::function<void ()> & onFinishEvent)124 void AnimateToForFaMode(const RefPtr<PipelineBase>& pipelineContext, AnimationOption& option,
125 const JSCallbackInfo& info, std::function<void()>& onFinishEvent)
126 {
127 pipelineContext->FlushBuild();
128 pipelineContext->OpenImplicitAnimation(option, option.GetCurve(), onFinishEvent);
129 pipelineContext->SetSyncAnimationOption(option);
130 JSRef<JSFunc> jsAnimateToFunc = JSRef<JSFunc>::Cast(info[1]);
131 jsAnimateToFunc->Call(info[1]);
132 pipelineContext->FlushBuild();
133 pipelineContext->SetSyncAnimationOption(AnimationOption());
134 pipelineContext->CloseImplicitAnimation();
135 }
136
137 } // namespace
138
CreateAnimation(const std::unique_ptr<JsonValue> & animationArgs,const std::function<float (float)> & jsFunc,bool isForm)139 const AnimationOption JSViewContext::CreateAnimation(
140 const std::unique_ptr<JsonValue>& animationArgs, const std::function<float(float)>& jsFunc, bool isForm)
141 {
142 AnimationOption option = AnimationOption();
143 if (!animationArgs) {
144 LOGW("CreateAnimation: animationArgs is null");
145 return option;
146 }
147 // If the attribute does not exist, the default value is used.
148 auto duration = animationArgs->GetInt("duration", DEFAULT_DURATION);
149 auto delay = animationArgs->GetInt("delay", 0);
150 auto iterations = animationArgs->GetInt("iterations", 1);
151 auto tempo = animationArgs->GetDouble("tempo", 1.0);
152 if (SystemProperties::GetRosenBackendEnabled() && NearZero(tempo)) {
153 // set duration to 0 to disable animation.
154 LOGI("tempo near 0, set duration to 0.");
155 duration = 0;
156 }
157 auto direction = StringToAnimationDirection(animationArgs->GetString("playMode", "normal"));
158 RefPtr<Curve> curve;
159 auto curveArgs = animationArgs->GetValue("curve");
160 if (curveArgs->IsString()) {
161 curve = CreateCurve(animationArgs->GetString("curve", DOM_ANIMATION_TIMING_FUNCTION_EASE_IN_OUT));
162 } else if (curveArgs->IsObject()) {
163 auto curveString = curveArgs->GetValue("__curveString");
164 if (!curveString) {
165 // Default AnimationOption which is invalid.
166 return option;
167 }
168 auto aniTimFunc = curveString->GetString();
169
170 std::string customFuncName(DOM_ANIMATION_TIMING_FUNCTION_CUSTOM);
171 if (aniTimFunc == customFuncName) {
172 curve = CreateCurve(jsFunc);
173 } else {
174 curve = CreateCurve(aniTimFunc);
175 }
176 } else {
177 curve = Curves::EASE_IN_OUT;
178 }
179
180 // limit animation for ArkTS Form
181 if (isForm) {
182 if (duration > static_cast<int32_t>(DEFAULT_DURATION)) {
183 LOGW("Form delay is not allowed to be set to a value greater than 1000ms, set it to 1000ms");
184 duration = static_cast<int32_t>(DEFAULT_DURATION);
185 }
186 if (delay != 0) {
187 LOGW("Form delay is not allowed to be set to a value other than 0, set it to 0");
188 delay = 0;
189 }
190 if (SystemProperties::IsFormAnimationLimited() && iterations != 1) {
191 LOGW("Form iterations is not allowed to be set to a value other than 1, set it to 1.");
192 iterations = 1;
193 }
194 if (!NearEqual(tempo, 1.0)) {
195 LOGW("Form tempo is not allowed to be set to a value other than 1.0, set it to 1.0.");
196 tempo = 1.0;
197 }
198 }
199
200 option.SetDuration(duration);
201 option.SetDelay(delay);
202 option.SetIteration(iterations);
203 option.SetTempo(tempo);
204 option.SetAnimationDirection(direction);
205 option.SetCurve(curve);
206 return option;
207 }
208
ParseCallBackFunction(const JSRef<JSObject> & obj)209 std::function<float(float)> ParseCallBackFunction(const JSRef<JSObject>& obj)
210 {
211 std::function<float(float)> customCallBack = nullptr;
212 JSRef<JSVal> curveVal = obj->GetProperty("curve");
213 if (curveVal->IsObject()) {
214 JSRef<JSObject> curveobj = JSRef<JSObject>::Cast(curveVal);
215 JSRef<JSVal> onCallBack = curveobj->GetProperty("__curveCustomFunc");
216 if (onCallBack->IsFunction()) {
217 RefPtr<JsFunction> jsFuncCallBack =
218 AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onCallBack));
219 customCallBack = [func = std::move(jsFuncCallBack), id = Container::CurrentId()](float time) -> float {
220 ContainerScope scope(id);
221 JSRef<JSVal> params[1];
222 params[0] = JSRef<JSVal>::Make(ToJSValue(time));
223 auto result = func->ExecuteJS(1, params);
224 auto resultValue = result->IsNumber() ? result->ToNumber<float>() : 1.0f;
225 if (resultValue < 0 || resultValue > 1) {
226 LOGI("The interpolate return value error = %{public}f ", resultValue);
227 }
228 return resultValue;
229 };
230 }
231 }
232 return customCallBack;
233 }
234
JSAnimation(const JSCallbackInfo & info)235 void JSViewContext::JSAnimation(const JSCallbackInfo& info)
236 {
237 ACE_FUNCTION_TRACE();
238 auto scopedDelegate = EngineHelper::GetCurrentDelegate();
239 if (!scopedDelegate) {
240 // this case usually means there is no foreground container, need to figure out the reason.
241 LOGE("scopedDelegate is null, please check");
242 return;
243 }
244 if (info.Length() < 1) {
245 LOGE("The arg is wrong, it is supposed to have 1 object argument.");
246 return;
247 }
248 AnimationOption option = AnimationOption();
249 auto container = Container::Current();
250 CHECK_NULL_VOID(container);
251 auto pipelineContextBase = container->GetPipelineContext();
252 CHECK_NULL_VOID(pipelineContextBase);
253 if (!pipelineContextBase->GetEnableImplicitAnimation() && pipelineContextBase->IsFormRender()) {
254 LOGW("Form need enable implicit animation in finish callback.");
255 return;
256 }
257 if (info[0]->IsNull() || !info[0]->IsObject()) {
258 ViewContextModel::GetInstance()->closeAnimation(option, true);
259 return;
260 }
261 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
262 JSRef<JSVal> onFinish = obj->GetProperty("onFinish");
263 std::function<void()> onFinishEvent;
264 if (onFinish->IsFunction()) {
265 RefPtr<JsFunction> jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onFinish));
266 onFinishEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc),
267 id = Container::CurrentId()]() {
268 ContainerScope scope(id);
269 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
270 func->Execute();
271 };
272 }
273 auto animationArgs = JsonUtil::ParseJsonString(info[0]->ToString());
274 if (animationArgs->IsNull()) {
275 LOGE("Js Parse failed. animationArgs is null.");
276 ViewContextModel::GetInstance()->closeAnimation(option, false);
277 return;
278 }
279
280 option = CreateAnimation(animationArgs, ParseCallBackFunction(obj), pipelineContextBase->IsFormRender());
281 option.SetOnFinishEvent(onFinishEvent);
282 if (SystemProperties::GetRosenBackendEnabled()) {
283 option.SetAllowRunningAsynchronously(true);
284 }
285 ViewContextModel::GetInstance()->openAnimation(option);
286 JankFrameReport::ReportJSAnimation();
287 }
288
JSAnimateTo(const JSCallbackInfo & info)289 void JSViewContext::JSAnimateTo(const JSCallbackInfo& info)
290 {
291 ACE_FUNCTION_TRACE();
292 auto scopedDelegate = EngineHelper::GetCurrentDelegate();
293 if (!scopedDelegate) {
294 // this case usually means there is no foreground container, need to figure out the reason.
295 LOGE("scopedDelegate is null, please check");
296 return;
297 }
298 if (info.Length() < 2) {
299 LOGE("The arg is wrong, it is supposed to have two arguments.");
300 return;
301 }
302 if (!info[0]->IsObject()) {
303 LOGE("1st argument is not object.");
304 return;
305 }
306 // 2nd argument should be a closure passed to the animateTo function.
307 if (!info[1]->IsFunction()) {
308 LOGE("2nd argument is not a function.");
309 return;
310 }
311
312 auto container = Container::Current();
313 CHECK_NULL_VOID(container);
314 auto pipelineContext = container->GetPipelineContext();
315 CHECK_NULL_VOID(pipelineContext);
316 if (!pipelineContext->GetEnableImplicitAnimation() && pipelineContext->IsFormRender()) {
317 LOGW("Form need enable implicit animation in finish callback.");
318 return;
319 }
320
321 JSRef<JSObject> obj = JSRef<JSObject>::Cast(info[0]);
322 JSRef<JSVal> onFinish = obj->GetProperty("onFinish");
323 std::function<void()> onFinishEvent;
324 if (onFinish->IsFunction()) {
325 RefPtr<JsFunction> jsFunc = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(), JSRef<JSFunc>::Cast(onFinish));
326 onFinishEvent = [execCtx = info.GetExecutionContext(), func = std::move(jsFunc),
327 id = Container::CurrentId()]() {
328 ContainerScope scope(id);
329 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
330 func->Execute();
331 };
332 }
333
334 auto animationArgs = JsonUtil::ParseJsonString(info[0]->ToString());
335 if (animationArgs->IsNull()) {
336 LOGE("Js Parse failed. animationArgs is null.");
337 return;
338 }
339
340 AnimationOption option =
341 CreateAnimation(animationArgs, ParseCallBackFunction(obj), pipelineContext->IsFormRender());
342 if (SystemProperties::GetRosenBackendEnabled()) {
343 bool usingSharedRuntime = container->GetSettings().usingSharedRuntime;
344 LOGD("RSAnimationInfo: Begin JSAnimateTo, usingSharedRuntime: %{public}d", usingSharedRuntime);
345 if (usingSharedRuntime) {
346 if (pipelineContext->IsLayouting()) {
347 LOGW("pipeline is layouting, post animateTo, duration:%{public}d, curve:%{public}s",
348 option.GetDuration(), option.GetCurve() ? option.GetCurve()->ToString().c_str() : "");
349 pipelineContext->GetTaskExecutor()->PostTask(
350 [id = Container::CurrentId(), option, func = JSRef<JSFunc>::Cast(info[1]),
351 onFinishEvent]() mutable {
352 ContainerScope scope(id);
353 auto container = Container::Current();
354 CHECK_NULL_VOID(container);
355 auto pipelineContext = container->GetPipelineContext();
356 CHECK_NULL_VOID(pipelineContext);
357 AnimateToForStageMode(pipelineContext, option, func, onFinishEvent);
358 },
359 TaskExecutor::TaskType::UI);
360 return;
361 }
362 AnimateToForStageMode(pipelineContext, option, JSRef<JSFunc>::Cast(info[1]), onFinishEvent);
363 } else {
364 AnimateToForFaMode(pipelineContext, option, info, onFinishEvent);
365 }
366 LOGD("RSAnimationInfo: End JSAnimateTo");
367 } else {
368 pipelineContext->FlushBuild();
369 pipelineContext->SaveExplicitAnimationOption(option);
370 // Execute the function.
371 JSRef<JSFunc> jsAnimateToFunc = JSRef<JSFunc>::Cast(info[1]);
372 jsAnimateToFunc->Call(info[1]);
373 pipelineContext->FlushBuild();
374 pipelineContext->CreateExplicitAnimator(onFinishEvent);
375 pipelineContext->ClearExplicitAnimationOption();
376 }
377 }
378
JSBind(BindingTarget globalObj)379 void JSViewContext::JSBind(BindingTarget globalObj)
380 {
381 JSClass<JSViewContext>::Declare("Context");
382 JSClass<JSViewContext>::StaticMethod("animation", JSAnimation);
383 JSClass<JSViewContext>::StaticMethod("animateTo", JSAnimateTo);
384 JSClass<JSViewContext>::Bind<>(globalObj);
385 }
386
387 } // namespace OHOS::Ace::Framework
388