1 /*
2 * Copyright (c) 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 "core/components_ng/pattern/stage/page_pattern.h"
17
18 #include "base/log/jank_frame_report.h"
19 #include "base/log/log_wrapper.h"
20 #include "base/perfmonitor/perf_monitor.h"
21 #include "base/utils/time_util.h"
22 #include "base/utils/utils.h"
23 #include "core/animation/animator.h"
24 #include "core/common/container.h"
25 #include "core/common/recorder/event_recorder.h"
26 #include "core/components/common/properties/alignment.h"
27 #include "core/pipeline_ng/pipeline_context.h"
28 #include "core/components_ng/base/observer_handler.h"
29 #include "bridge/common/utils/engine_helper.h"
30
31 namespace OHOS::Ace::NG {
32
33 namespace {
34 std::string KEY_PAGE_TRANSITION_PROPERTY = "pageTransitionProperty";
IterativeAddToSharedMap(const RefPtr<UINode> & node,SharedTransitionMap & map)35 void IterativeAddToSharedMap(const RefPtr<UINode>& node, SharedTransitionMap& map)
36 {
37 const auto& children = node->GetChildren();
38 for (const auto& child : children) {
39 auto frameChild = AceType::DynamicCast<FrameNode>(child);
40 if (!frameChild) {
41 IterativeAddToSharedMap(child, map);
42 continue;
43 }
44 auto id = frameChild->GetRenderContext()->GetShareId();
45 if (!id.empty()) {
46 map[id] = frameChild;
47 }
48 IterativeAddToSharedMap(frameChild, map);
49 }
50 }
51 } // namespace
52
OnAttachToFrameNode()53 void PagePattern::OnAttachToFrameNode()
54 {
55 auto host = GetHost();
56 CHECK_NULL_VOID(host);
57 MeasureType measureType = MeasureType::MATCH_PARENT;
58 auto container = Container::Current();
59 if (container && container->IsDynamicRender()) {
60 measureType = MeasureType::MATCH_CONTENT;
61 }
62 host->GetLayoutProperty()->UpdateMeasureType(measureType);
63 host->GetLayoutProperty()->UpdateAlignment(Alignment::TOP_LEFT);
64 }
65
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & wrapper,const DirtySwapConfig &)66 bool PagePattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& wrapper, const DirtySwapConfig& /* config */)
67 {
68 if (isFirstLoad_) {
69 isFirstLoad_ = false;
70 if (firstBuildCallback_) {
71 firstBuildCallback_();
72 firstBuildCallback_ = nullptr;
73 }
74 }
75 if (dynamicPageSizeCallback_) {
76 auto node = wrapper->GetGeometryNode();
77 CHECK_NULL_RETURN(node, false);
78 dynamicPageSizeCallback_(node->GetFrameSize());
79 }
80 return false;
81 }
82
TriggerPageTransition(PageTransitionType type,const std::function<void ()> & onFinish)83 bool PagePattern::TriggerPageTransition(PageTransitionType type, const std::function<void()>& onFinish)
84 {
85 auto host = GetHost();
86 CHECK_NULL_RETURN(host, false);
87 auto renderContext = host->GetRenderContext();
88 CHECK_NULL_RETURN(renderContext, false);
89 if (pageTransitionFunc_) {
90 pageTransitionFunc_();
91 }
92 auto effect = FindPageTransitionEffect(type);
93 pageTransitionFinish_ = std::make_shared<std::function<void()>>(onFinish);
94 auto wrappedOnFinish = [weak = WeakClaim(this), sharedFinish = pageTransitionFinish_]() {
95 auto pattern = weak.Upgrade();
96 CHECK_NULL_VOID(pattern);
97 auto host = pattern->GetHost();
98 CHECK_NULL_VOID(host);
99 if (sharedFinish == pattern->pageTransitionFinish_) {
100 // ensure this is exactly the finish callback saved in pagePattern,
101 // otherwise means new pageTransition started
102 pattern->FirePageTransitionFinish();
103 host->DeleteAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY);
104 }
105 };
106 if (effect && effect->GetUserCallback()) {
107 RouteType routeType = (type == PageTransitionType::ENTER_POP || type == PageTransitionType::EXIT_POP)
108 ? RouteType::POP
109 : RouteType::PUSH;
110 host->CreateAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY, 0.0f,
111 [routeType, handler = effect->GetUserCallback()](const float& progress) { handler(routeType, progress); });
112 auto handler = effect->GetUserCallback();
113 handler(routeType, 0.0f);
114 AnimationOption option(effect->GetCurve(), effect->GetDuration());
115 option.SetDelay(effect->GetDelay());
116 AnimationUtils::OpenImplicitAnimation(option, option.GetCurve(), wrappedOnFinish);
117 host->UpdateAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY, 1.0f);
118 AnimationUtils::CloseImplicitAnimation();
119 return renderContext->TriggerPageTransition(type, nullptr);
120 }
121 return renderContext->TriggerPageTransition(type, wrappedOnFinish);
122 }
123
ProcessAutoSave()124 void PagePattern::ProcessAutoSave()
125 {
126 auto host = GetHost();
127 CHECK_NULL_VOID(host);
128 if (!host->NeedRequestAutoSave()) {
129 return;
130 }
131 auto container = Container::Current();
132 CHECK_NULL_VOID(container);
133 container->RequestAutoSave(host);
134 }
135
ProcessHideState()136 void PagePattern::ProcessHideState()
137 {
138 auto host = GetHost();
139 CHECK_NULL_VOID(host);
140 host->SetActive(false);
141 host->OnVisibleChange(false);
142 host->GetLayoutProperty()->UpdateVisibility(VisibleType::INVISIBLE);
143 auto parent = host->GetAncestorNodeOfFrame();
144 CHECK_NULL_VOID(parent);
145 parent->MarkNeedSyncRenderTree();
146 parent->RebuildRenderContextTree();
147 }
148
ProcessShowState()149 void PagePattern::ProcessShowState()
150 {
151 auto host = GetHost();
152 CHECK_NULL_VOID(host);
153 host->SetActive(true);
154 host->OnVisibleChange(true);
155 host->GetLayoutProperty()->UpdateVisibility(VisibleType::VISIBLE);
156 auto parent = host->GetAncestorNodeOfFrame();
157 CHECK_NULL_VOID(parent);
158 auto context = NG::PipelineContext::GetCurrentContext();
159 CHECK_NULL_VOID(context);
160 auto manager = context->GetSafeAreaManager();
161 if (manager) {
162 auto safeArea = manager->GetSafeArea();
163 auto parentGlobalOffset = host->GetParentGlobalOffsetDuringLayout();
164 auto geometryNode = host->GetGeometryNode();
165 auto frame = geometryNode->GetFrameRect() + parentGlobalOffset;
166 // if page's frameRect not fit current safeArea, need layout page again
167 if (!NearEqual(frame.GetY(), safeArea.top_.end)) {
168 host->MarkDirtyNode(manager->KeyboardSafeAreaEnabled() ? PROPERTY_UPDATE_LAYOUT : PROPERTY_UPDATE_MEASURE);
169 }
170 if (!NearEqual(frame.GetY() + frame.Height(), safeArea.bottom_.start)) {
171 host->MarkDirtyNode(manager->KeyboardSafeAreaEnabled() ? PROPERTY_UPDATE_LAYOUT : PROPERTY_UPDATE_MEASURE);
172 }
173 }
174 parent->MarkNeedSyncRenderTree();
175 parent->RebuildRenderContextTree();
176 }
177
OnAttachToMainTree()178 void PagePattern::OnAttachToMainTree()
179 {
180 std::string url = GetPageInfo()->GetPageUrl();
181 int32_t index = EngineHelper::GetCurrentDelegate()->GetIndexByUrl(url);
182 GetPageInfo()->SetPageIndex(index);
183 state_ = RouterPageState::ABOUT_TO_APPEAR;
184 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
185 }
186
OnDetachFromMainTree()187 void PagePattern::OnDetachFromMainTree()
188 {
189 state_ = RouterPageState::ABOUT_TO_DISAPPEAR;
190 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
191 }
192
OnShow()193 void PagePattern::OnShow()
194 {
195 // Do not invoke onPageShow unless the initialRender function has been executed.
196 CHECK_NULL_VOID(isRenderDone_);
197 CHECK_NULL_VOID(!isOnShow_);
198 auto context = NG::PipelineContext::GetCurrentContext();
199 CHECK_NULL_VOID(context);
200 if (pageInfo_) {
201 context->FirePageChanged(pageInfo_->GetPageId(), true);
202 }
203 auto container = Container::Current();
204 if (!container || !container->WindowIsShow()) {
205 LOGW("no need to trigger onPageShow callback when not in the foreground");
206 return;
207 }
208 auto host = GetHost();
209 CHECK_NULL_VOID(host);
210 host->SetJSViewActive(true);
211 isOnShow_ = true;
212 state_ = RouterPageState::ON_PAGE_SHOW;
213 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
214 JankFrameReport::GetInstance().StartRecord(pageInfo_->GetPageUrl());
215 PerfMonitor::GetPerfMonitor()->SetPageUrl(pageInfo_->GetPageUrl());
216 auto pageUrlChecker = container->GetPageUrlChecker();
217 if (pageUrlChecker != nullptr) {
218 pageUrlChecker->NotifyPageShow(pageInfo_->GetPageUrl());
219 }
220 if (onPageShow_) {
221 onPageShow_();
222 }
223 if (Recorder::EventRecorder::Get().IsPageRecordEnable()) {
224 std::string param;
225 auto entryPageInfo = DynamicCast<EntryPageInfo>(pageInfo_);
226 if (entryPageInfo) {
227 param = entryPageInfo->GetPageParams();
228 entryPageInfo->SetShowTime(GetCurrentTimestamp());
229 }
230 Recorder::EventRecorder::Get().OnPageShow(pageInfo_->GetPageUrl(), param);
231 }
232 }
233
OnHide()234 void PagePattern::OnHide()
235 {
236 CHECK_NULL_VOID(isOnShow_);
237 JankFrameReport::GetInstance().FlushRecord();
238 auto context = NG::PipelineContext::GetCurrentContext();
239 CHECK_NULL_VOID(context);
240 if (pageInfo_) {
241 context->FirePageChanged(pageInfo_->GetPageId(), false);
242 }
243 auto host = GetHost();
244 CHECK_NULL_VOID(host);
245 host->SetJSViewActive(false);
246 isOnShow_ = false;
247 state_ = RouterPageState::ON_PAGE_HIDE;
248 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
249 auto container = Container::Current();
250 if (container) {
251 auto pageUrlChecker = container->GetPageUrlChecker();
252 // ArkTSCard container no SetPageUrlChecker
253 if (pageUrlChecker != nullptr) {
254 pageUrlChecker->NotifyPageHide(pageInfo_->GetPageUrl());
255 }
256 }
257 if (onPageHide_) {
258 onPageHide_();
259 }
260 if (Recorder::EventRecorder::Get().IsPageRecordEnable()) {
261 auto entryPageInfo = DynamicCast<EntryPageInfo>(pageInfo_);
262 int64_t duration = 0;
263 if (entryPageInfo && entryPageInfo->GetShowTime() > 0) {
264 duration = GetCurrentTimestamp() - entryPageInfo->GetShowTime();
265 }
266 Recorder::EventRecorder::Get().OnPageHide(pageInfo_->GetPageUrl(), duration);
267 }
268 }
269
OnBackPressed()270 bool PagePattern::OnBackPressed()
271 {
272 if (isPageInTransition_) {
273 return true;
274 }
275 // if in page transition, do not set to ON_BACK_PRESS
276 state_ = RouterPageState::ON_BACK_PRESS;
277 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
278 if (onBackPressed_) {
279 return onBackPressed_();
280 }
281 return false;
282 }
283
BuildSharedTransitionMap()284 void PagePattern::BuildSharedTransitionMap()
285 {
286 auto host = GetHost();
287 CHECK_NULL_VOID(host);
288 sharedTransitionMap_.clear();
289 IterativeAddToSharedMap(host, sharedTransitionMap_);
290 }
291
ReloadPage()292 void PagePattern::ReloadPage()
293 {
294 auto host = GetHost();
295 CHECK_NULL_VOID(host);
296 auto customNode = DynamicCast<CustomNodeBase>(host->GetFirstChild());
297 CHECK_NULL_VOID(customNode);
298 customNode->FireReloadFunction(true);
299 }
300
FindPageTransitionEffect(PageTransitionType type)301 RefPtr<PageTransitionEffect> PagePattern::FindPageTransitionEffect(PageTransitionType type)
302 {
303 RefPtr<PageTransitionEffect> result;
304 for (auto iter = pageTransitionEffects_.rbegin(); iter != pageTransitionEffects_.rend(); ++iter) {
305 auto effect = *iter;
306 if (effect->CanFit(type)) {
307 result = effect;
308 break;
309 }
310 }
311 return result;
312 }
313
ClearPageTransitionEffect()314 void PagePattern::ClearPageTransitionEffect()
315 {
316 pageTransitionEffects_.clear();
317 }
318
GetTopTransition() const319 RefPtr<PageTransitionEffect> PagePattern::GetTopTransition() const
320 {
321 return pageTransitionEffects_.empty() ? nullptr : pageTransitionEffects_.back();
322 }
323
AddPageTransition(const RefPtr<PageTransitionEffect> & effect)324 void PagePattern::AddPageTransition(const RefPtr<PageTransitionEffect>& effect)
325 {
326 pageTransitionEffects_.emplace_back(effect);
327 }
328
AddJsAnimator(const std::string & animatorId,const RefPtr<Framework::AnimatorInfo> & animatorInfo)329 void PagePattern::AddJsAnimator(const std::string& animatorId, const RefPtr<Framework::AnimatorInfo>& animatorInfo)
330 {
331 CHECK_NULL_VOID(animatorInfo);
332 auto animator = animatorInfo->GetAnimator();
333 CHECK_NULL_VOID(animator);
334 animator->AttachScheduler(PipelineContext::GetCurrentContext());
335 jsAnimatorMap_[animatorId] = animatorInfo;
336 }
337
GetJsAnimator(const std::string & animatorId)338 RefPtr<Framework::AnimatorInfo> PagePattern::GetJsAnimator(const std::string& animatorId)
339 {
340 auto iter = jsAnimatorMap_.find(animatorId);
341 if (iter != jsAnimatorMap_.end()) {
342 return iter->second;
343 }
344 return nullptr;
345 }
346
SetFirstBuildCallback(std::function<void ()> && buildCallback)347 void PagePattern::SetFirstBuildCallback(std::function<void()>&& buildCallback)
348 {
349 if (isFirstLoad_) {
350 firstBuildCallback_ = std::move(buildCallback);
351 } else if (buildCallback) {
352 buildCallback();
353 }
354 }
355
FirePageTransitionFinish()356 void PagePattern::FirePageTransitionFinish()
357 {
358 if (pageTransitionFinish_) {
359 auto onFinish = *pageTransitionFinish_;
360 pageTransitionFinish_ = nullptr;
361 if (onFinish) {
362 onFinish();
363 }
364 }
365 }
366
StopPageTransition()367 void PagePattern::StopPageTransition()
368 {
369 auto host = GetHost();
370 CHECK_NULL_VOID(host);
371 auto property = host->GetAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY);
372 if (property) {
373 FirePageTransitionFinish();
374 return;
375 }
376 AnimationOption option(Curves::LINEAR, 0);
377 AnimationUtils::Animate(
378 option, [host]() { host->UpdateAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY, 0.0f); },
379 nullptr);
380 host->DeleteAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY);
381 FirePageTransitionFinish();
382 }
383
BeforeCreateLayoutWrapper()384 void PagePattern::BeforeCreateLayoutWrapper()
385 {
386 auto pipeline = PipelineContext::GetCurrentContext();
387 CHECK_NULL_VOID(pipeline);
388 // SafeArea already applied to AppBar (AtomicServicePattern)
389 if (pipeline->GetInstallationFree()) {
390 return;
391 }
392 ContentRootPattern::BeforeCreateLayoutWrapper();
393 }
394
AvoidKeyboard() const395 bool PagePattern::AvoidKeyboard() const
396 {
397 auto pipeline = PipelineContext::GetCurrentContext();
398 CHECK_NULL_RETURN(pipeline, false);
399 return pipeline->GetSafeAreaManager()->KeyboardSafeAreaEnabled();
400 }
401 } // namespace OHOS::Ace::NG
402