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_constants.h"
21 #include "base/perfmonitor/perf_monitor.h"
22 #include "base/utils/time_util.h"
23 #include "base/utils/utils.h"
24 #include "core/animation/animator.h"
25 #include "core/common/container.h"
26 #include "core/common/recorder/event_recorder.h"
27 #include "core/components/common/properties/alignment.h"
28 #include "core/pipeline_ng/pipeline_context.h"
29 #include "core/components_ng/base/observer_handler.h"
30 #include "bridge/common/utils/engine_helper.h"
31
32 namespace OHOS::Ace::NG {
33
34 namespace {
35 constexpr int32_t INVALID_PAGE_INDEX = -1;
36 std::string KEY_PAGE_TRANSITION_PROPERTY = "pageTransitionProperty";
IterativeAddToSharedMap(const RefPtr<UINode> & node,SharedTransitionMap & map)37 void IterativeAddToSharedMap(const RefPtr<UINode>& node, SharedTransitionMap& map)
38 {
39 const auto& children = node->GetChildren();
40 for (const auto& child : children) {
41 auto frameChild = AceType::DynamicCast<FrameNode>(child);
42 if (!frameChild) {
43 IterativeAddToSharedMap(child, map);
44 continue;
45 }
46 auto id = frameChild->GetRenderContext()->GetShareId();
47 if (!id.empty()) {
48 map[id] = frameChild;
49 }
50 IterativeAddToSharedMap(frameChild, map);
51 }
52 }
53 } // namespace
54
OnAttachToFrameNode()55 void PagePattern::OnAttachToFrameNode()
56 {
57 auto host = GetHost();
58 CHECK_NULL_VOID(host);
59 MeasureType measureType = MeasureType::MATCH_PARENT;
60 auto container = Container::Current();
61 if (container && container->IsDynamicRender()) {
62 measureType = MeasureType::MATCH_CONTENT;
63 }
64 host->GetLayoutProperty()->UpdateMeasureType(measureType);
65 host->GetLayoutProperty()->UpdateAlignment(Alignment::TOP_LEFT);
66 }
67
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & wrapper,const DirtySwapConfig &)68 bool PagePattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& wrapper, const DirtySwapConfig& /* config */)
69 {
70 if (isFirstLoad_) {
71 isFirstLoad_ = false;
72 if (firstBuildCallback_) {
73 firstBuildCallback_();
74 firstBuildCallback_ = nullptr;
75 }
76 }
77 return false;
78 }
79
BeforeSyncGeometryProperties(const DirtySwapConfig & config)80 void PagePattern::BeforeSyncGeometryProperties(const DirtySwapConfig& config)
81 {
82 if (config.skipLayout || config.skipMeasure) {
83 return;
84 }
85 CHECK_NULL_VOID(dynamicPageSizeCallback_);
86 auto host = GetHost();
87 CHECK_NULL_VOID(host);
88 auto node = host->GetGeometryNode();
89 CHECK_NULL_VOID(node);
90 dynamicPageSizeCallback_(node->GetFrameSize());
91 }
92
TriggerPageTransition(PageTransitionType type,const std::function<void ()> & onFinish)93 bool PagePattern::TriggerPageTransition(PageTransitionType type, const std::function<void()>& onFinish)
94 {
95 auto host = GetHost();
96 CHECK_NULL_RETURN(host, false);
97 auto renderContext = host->GetRenderContext();
98 CHECK_NULL_RETURN(renderContext, false);
99 if (pageTransitionFunc_) {
100 pageTransitionFunc_();
101 }
102 auto effect = FindPageTransitionEffect(type);
103 pageTransitionFinish_ = std::make_shared<std::function<void()>>(onFinish);
104 auto wrappedOnFinish = [weak = WeakClaim(this), sharedFinish = pageTransitionFinish_, transitionType = type]() {
105 auto pattern = weak.Upgrade();
106 CHECK_NULL_VOID(pattern);
107 if (transitionType == PageTransitionType::ENTER_PUSH || transitionType == PageTransitionType::ENTER_POP) {
108 ACE_SCOPED_TRACE_COMMERCIAL("Router Page Transition End");
109 PerfMonitor::GetPerfMonitor()->End(PerfConstants::ABILITY_OR_PAGE_SWITCH, true);
110 }
111 auto host = pattern->GetHost();
112 CHECK_NULL_VOID(host);
113 if (sharedFinish == pattern->pageTransitionFinish_) {
114 // ensure this is exactly the finish callback saved in pagePattern,
115 // otherwise means new pageTransition started
116 pattern->FirePageTransitionFinish();
117 host->DeleteAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY);
118 }
119 };
120 if (effect && effect->GetUserCallback()) {
121 RouteType routeType = (type == PageTransitionType::ENTER_POP || type == PageTransitionType::EXIT_POP)
122 ? RouteType::POP
123 : RouteType::PUSH;
124 host->CreateAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY, 0.0f,
125 [routeType, handler = effect->GetUserCallback()](const float& progress) { handler(routeType, progress); });
126 auto handler = effect->GetUserCallback();
127 handler(routeType, 0.0f);
128 AnimationOption option(effect->GetCurve(), effect->GetDuration());
129 option.SetDelay(effect->GetDelay());
130 AnimationUtils::OpenImplicitAnimation(option, option.GetCurve(), wrappedOnFinish);
131 host->UpdateAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY, 1.0f);
132 AnimationUtils::CloseImplicitAnimation();
133 return renderContext->TriggerPageTransition(type, nullptr);
134 }
135 return renderContext->TriggerPageTransition(type, wrappedOnFinish);
136 }
137
ProcessAutoSave(const std::function<void ()> & onFinish,const std::function<void ()> & onUIExtNodeBindingCompleted)138 bool PagePattern::ProcessAutoSave(const std::function<void()>& onFinish,
139 const std::function<void()>& onUIExtNodeBindingCompleted)
140 {
141 auto host = GetHost();
142 CHECK_NULL_RETURN(host, false);
143 if (!host->NeedRequestAutoSave()) {
144 return false;
145 }
146 auto container = Container::Current();
147 CHECK_NULL_RETURN(container, false);
148 return container->RequestAutoSave(host, onFinish, onUIExtNodeBindingCompleted);
149 }
150
ProcessHideState()151 void PagePattern::ProcessHideState()
152 {
153 auto host = GetHost();
154 CHECK_NULL_VOID(host);
155 host->SetActive(false);
156 host->NotifyVisibleChange(VisibleType::VISIBLE, VisibleType::INVISIBLE);
157 host->GetLayoutProperty()->UpdateVisibility(VisibleType::INVISIBLE);
158 auto parent = host->GetAncestorNodeOfFrame();
159 CHECK_NULL_VOID(parent);
160 parent->MarkNeedSyncRenderTree();
161 parent->RebuildRenderContextTree();
162 }
163
ProcessShowState()164 void PagePattern::ProcessShowState()
165 {
166 auto host = GetHost();
167 CHECK_NULL_VOID(host);
168 host->SetActive(true);
169 host->NotifyVisibleChange(VisibleType::INVISIBLE, VisibleType::VISIBLE);
170 host->GetLayoutProperty()->UpdateVisibility(VisibleType::VISIBLE);
171 auto parent = host->GetAncestorNodeOfFrame();
172 CHECK_NULL_VOID(parent);
173 auto context = NG::PipelineContext::GetCurrentContext();
174 CHECK_NULL_VOID(context);
175 auto manager = context->GetSafeAreaManager();
176 if (manager) {
177 auto safeArea = manager->GetSafeArea();
178 auto parentGlobalOffset = host->GetParentGlobalOffsetDuringLayout();
179 auto frame = host->GetPaintRectWithTransform() + parentGlobalOffset;
180 // if page's frameRect not fit current safeArea, need layout page again
181 if (!NearEqual(frame.GetY(), safeArea.top_.end)) {
182 host->MarkDirtyNode(manager->KeyboardSafeAreaEnabled() ? PROPERTY_UPDATE_LAYOUT : PROPERTY_UPDATE_MEASURE);
183 }
184 if (!NearEqual(frame.GetY() + frame.Height(), safeArea.bottom_.start)) {
185 host->MarkDirtyNode(manager->KeyboardSafeAreaEnabled() ? PROPERTY_UPDATE_LAYOUT : PROPERTY_UPDATE_MEASURE);
186 }
187 }
188 parent->MarkNeedSyncRenderTree();
189 parent->RebuildRenderContextTree();
190 }
191
OnAttachToMainTree()192 void PagePattern::OnAttachToMainTree()
193 {
194 #if defined(ENABLE_SPLIT_MODE)
195 if (!needFireObserver_) {
196 return;
197 }
198 #endif
199 int32_t index = INVALID_PAGE_INDEX;
200 auto delegate = EngineHelper::GetCurrentDelegate();
201 if (delegate) {
202 index = delegate->GetCurrentPageIndex();
203 GetPageInfo()->SetPageIndex(index);
204 }
205 state_ = RouterPageState::ABOUT_TO_APPEAR;
206 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
207 }
208
OnDetachFromMainTree()209 void PagePattern::OnDetachFromMainTree()
210 {
211 #if defined(ENABLE_SPLIT_MODE)
212 if (!needFireObserver_) {
213 return;
214 }
215 #endif
216 state_ = RouterPageState::ABOUT_TO_DISAPPEAR;
217 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
218 }
219
OnShow()220 void PagePattern::OnShow()
221 {
222 // Do not invoke onPageShow unless the initialRender function has been executed.
223 CHECK_NULL_VOID(isRenderDone_);
224 CHECK_NULL_VOID(!isOnShow_);
225 auto context = NG::PipelineContext::GetCurrentContext();
226 CHECK_NULL_VOID(context);
227 auto container = Container::Current();
228 if (!container || !container->WindowIsShow()) {
229 LOGW("no need to trigger onPageShow callback when not in the foreground");
230 return;
231 }
232 std::string bundleName = container->GetBundleName();
233 NotifyPerfMonitorPageMsg(pageInfo_->GetFullPath(), bundleName);
234 if (pageInfo_) {
235 context->FirePageChanged(pageInfo_->GetPageId(), true);
236 }
237 UpdatePageParam();
238 isOnShow_ = true;
239 #if defined(ENABLE_SPLIT_MODE)
240 if (needFireObserver_) {
241 state_ = RouterPageState::ON_PAGE_SHOW;
242 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
243 }
244 #else
245 state_ = RouterPageState::ON_PAGE_SHOW;
246 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
247 #endif
248 JankFrameReport::GetInstance().StartRecord(pageInfo_->GetFullPath());
249 auto pageUrlChecker = container->GetPageUrlChecker();
250 if (pageUrlChecker != nullptr) {
251 pageUrlChecker->NotifyPageShow(pageInfo_->GetPageUrl());
252 }
253 if (visibilityChangeCallback_) {
254 visibilityChangeCallback_(true);
255 }
256 if (onPageShow_) {
257 onPageShow_();
258 }
259 if (!onHiddenChange_.empty()) {
260 FireOnHiddenChange(true);
261 }
262 if (Recorder::EventRecorder::Get().IsPageRecordEnable()) {
263 std::string param;
264 auto entryPageInfo = DynamicCast<EntryPageInfo>(pageInfo_);
265 if (entryPageInfo) {
266 param = Recorder::EventRecorder::Get().IsPageParamRecordEnable() ? entryPageInfo->GetPageParams() : "";
267 entryPageInfo->SetShowTime(GetCurrentTimestamp());
268 }
269 Recorder::EventRecorder::Get().OnPageShow(pageInfo_->GetPageUrl(), param);
270 }
271 }
272
OnHide()273 void PagePattern::OnHide()
274 {
275 CHECK_NULL_VOID(isOnShow_);
276 JankFrameReport::GetInstance().FlushRecord();
277 auto context = NG::PipelineContext::GetCurrentContext();
278 CHECK_NULL_VOID(context);
279 if (pageInfo_) {
280 context->FirePageChanged(pageInfo_->GetPageId(), false);
281 }
282 auto host = GetHost();
283 CHECK_NULL_VOID(host);
284 host->SetJSViewActive(false);
285 isOnShow_ = false;
286 host->SetAccessibilityVisible(false);
287 #if defined(ENABLE_SPLIT_MODE)
288 if (needFireObserver_) {
289 state_ = RouterPageState::ON_PAGE_HIDE;
290 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
291 }
292 #else
293 state_ = RouterPageState::ON_PAGE_HIDE;
294 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
295 #endif
296 auto container = Container::Current();
297 if (container) {
298 auto pageUrlChecker = container->GetPageUrlChecker();
299 // ArkTSCard container no SetPageUrlChecker
300 if (pageUrlChecker != nullptr) {
301 pageUrlChecker->NotifyPageHide(pageInfo_->GetPageUrl());
302 }
303 }
304 if (visibilityChangeCallback_) {
305 visibilityChangeCallback_(false);
306 }
307 if (onPageHide_) {
308 onPageHide_();
309 }
310 if (!onHiddenChange_.empty()) {
311 FireOnHiddenChange(false);
312 }
313 if (Recorder::EventRecorder::Get().IsPageRecordEnable()) {
314 auto entryPageInfo = DynamicCast<EntryPageInfo>(pageInfo_);
315 int64_t duration = 0;
316 if (entryPageInfo && entryPageInfo->GetShowTime() > 0) {
317 duration = GetCurrentTimestamp() - entryPageInfo->GetShowTime();
318 }
319 Recorder::EventRecorder::Get().OnPageHide(pageInfo_->GetPageUrl(), duration);
320 }
321 }
322
OnBackPressed()323 bool PagePattern::OnBackPressed()
324 {
325 if (RemoveOverlay()) {
326 TAG_LOGI(AceLogTag::ACE_OVERLAY, "page removes it's overlay when on backpressed");
327 return true;
328 }
329 if (isPageInTransition_) {
330 TAG_LOGI(AceLogTag::ACE_ROUTER, "page is in transition");
331 return true;
332 }
333 // if in page transition, do not set to ON_BACK_PRESS
334 #if defined(ENABLE_SPLIT_MODE)
335 if (needFireObserver_) {
336 state_ = RouterPageState::ON_BACK_PRESS;
337 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
338 }
339 #else
340 state_ = RouterPageState::ON_BACK_PRESS;
341 UIObserverHandler::GetInstance().NotifyRouterPageStateChange(GetPageInfo(), state_);
342 #endif
343 if (onBackPressed_) {
344 return onBackPressed_();
345 }
346 return false;
347 }
348
BuildSharedTransitionMap()349 void PagePattern::BuildSharedTransitionMap()
350 {
351 auto host = GetHost();
352 CHECK_NULL_VOID(host);
353 sharedTransitionMap_.clear();
354 IterativeAddToSharedMap(host, sharedTransitionMap_);
355 }
356
ReloadPage()357 void PagePattern::ReloadPage()
358 {
359 auto host = GetHost();
360 CHECK_NULL_VOID(host);
361 auto customNode = DynamicCast<CustomNodeBase>(host->GetFirstChild());
362 CHECK_NULL_VOID(customNode);
363 customNode->FireReloadFunction(true);
364 }
365
FindPageTransitionEffect(PageTransitionType type)366 RefPtr<PageTransitionEffect> PagePattern::FindPageTransitionEffect(PageTransitionType type)
367 {
368 RefPtr<PageTransitionEffect> result;
369 for (auto iter = pageTransitionEffects_.rbegin(); iter != pageTransitionEffects_.rend(); ++iter) {
370 auto effect = *iter;
371 if (effect->CanFit(type)) {
372 result = effect;
373 break;
374 }
375 }
376 return result;
377 }
378
ClearPageTransitionEffect()379 void PagePattern::ClearPageTransitionEffect()
380 {
381 pageTransitionEffects_.clear();
382 }
383
GetTopTransition() const384 RefPtr<PageTransitionEffect> PagePattern::GetTopTransition() const
385 {
386 return pageTransitionEffects_.empty() ? nullptr : pageTransitionEffects_.back();
387 }
388
AddPageTransition(const RefPtr<PageTransitionEffect> & effect)389 void PagePattern::AddPageTransition(const RefPtr<PageTransitionEffect>& effect)
390 {
391 pageTransitionEffects_.emplace_back(effect);
392 }
393
AddJsAnimator(const std::string & animatorId,const RefPtr<Framework::AnimatorInfo> & animatorInfo)394 void PagePattern::AddJsAnimator(const std::string& animatorId, const RefPtr<Framework::AnimatorInfo>& animatorInfo)
395 {
396 CHECK_NULL_VOID(animatorInfo);
397 auto animator = animatorInfo->GetAnimator();
398 CHECK_NULL_VOID(animator);
399 animator->AttachScheduler(PipelineContext::GetCurrentContext());
400 jsAnimatorMap_[animatorId] = animatorInfo;
401 }
402
GetJsAnimator(const std::string & animatorId)403 RefPtr<Framework::AnimatorInfo> PagePattern::GetJsAnimator(const std::string& animatorId)
404 {
405 auto iter = jsAnimatorMap_.find(animatorId);
406 if (iter != jsAnimatorMap_.end()) {
407 return iter->second;
408 }
409 return nullptr;
410 }
411
SetFirstBuildCallback(std::function<void ()> && buildCallback)412 void PagePattern::SetFirstBuildCallback(std::function<void()>&& buildCallback)
413 {
414 if (isFirstLoad_) {
415 firstBuildCallback_ = std::move(buildCallback);
416 } else if (buildCallback) {
417 buildCallback();
418 }
419 }
420
FirePageTransitionFinish()421 void PagePattern::FirePageTransitionFinish()
422 {
423 if (pageTransitionFinish_) {
424 auto onFinish = *pageTransitionFinish_;
425 pageTransitionFinish_ = nullptr;
426 if (onFinish) {
427 onFinish();
428 }
429 }
430 }
431
StopPageTransition()432 void PagePattern::StopPageTransition()
433 {
434 auto host = GetHost();
435 CHECK_NULL_VOID(host);
436 auto property = host->GetAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY);
437 if (property) {
438 FirePageTransitionFinish();
439 return;
440 }
441 AnimationOption option(Curves::LINEAR, 0);
442 AnimationUtils::Animate(
443 option, [host]() { host->UpdateAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY, 0.0f); },
444 nullptr);
445 host->DeleteAnimatablePropertyFloat(KEY_PAGE_TRANSITION_PROPERTY);
446 FirePageTransitionFinish();
447 }
448
BeforeCreateLayoutWrapper()449 void PagePattern::BeforeCreateLayoutWrapper()
450 {
451 auto pipeline = PipelineContext::GetCurrentContext();
452 CHECK_NULL_VOID(pipeline);
453 // SafeArea already applied to AppBar (AtomicServicePattern)
454 if (pipeline->GetInstallationFree()) {
455 auto host = GetHost();
456 CHECK_NULL_VOID(host);
457 ACE_SCOPED_TRACE("[%s][self:%d] SafeArea already applied to AppBar", host->GetTag().c_str(), host->GetId());
458 return;
459 }
460 ContentRootPattern::BeforeCreateLayoutWrapper();
461 auto host = GetHost();
462 CHECK_NULL_VOID(host);
463 auto&& insets = host->GetLayoutProperty()->GetSafeAreaInsets();
464 CHECK_NULL_VOID(insets);
465 auto manager = pipeline->GetSafeAreaManager();
466 CHECK_NULL_VOID(manager);
467 ACE_SCOPED_TRACE("[%s][self:%d] safeAreaInsets: AvoidKeyboard %d, AvoidTop %d, AvoidCutout "
468 "%d, AvoidBottom %d insets %s isIgnore: %d, isNeedAvoidWindow %d, "
469 "isFullScreen %d",
470 host->GetTag().c_str(), host->GetId(), AvoidKeyboard(), AvoidTop(), AvoidCutout(), AvoidBottom(),
471 insets->ToString().c_str(), manager->IsIgnoreAsfeArea(), manager->IsNeedAvoidWindow(), manager->IsFullScreen());
472 }
473
AvoidKeyboard() const474 bool PagePattern::AvoidKeyboard() const
475 {
476 auto pipeline = PipelineContext::GetCurrentContext();
477 CHECK_NULL_RETURN(pipeline, false);
478 return pipeline->GetSafeAreaManager()->KeyboardSafeAreaEnabled();
479 }
480
RemoveOverlay()481 bool PagePattern::RemoveOverlay()
482 {
483 CHECK_NULL_RETURN(overlayManager_, false);
484 CHECK_NULL_RETURN(!overlayManager_->IsModalEmpty(), false);
485 auto pipeline = PipelineContext::GetCurrentContext();
486 CHECK_NULL_RETURN(pipeline, false);
487 auto taskExecutor = pipeline->GetTaskExecutor();
488 CHECK_NULL_RETURN(taskExecutor, false);
489 return overlayManager_->RemoveOverlay(true);
490 }
491
NotifyPerfMonitorPageMsg(const std::string & pageUrl,const std::string & bundleName)492 void PagePattern::NotifyPerfMonitorPageMsg(const std::string& pageUrl, const std::string& bundleName)
493 {
494 if (PerfMonitor::GetPerfMonitor() != nullptr) {
495 PerfMonitor::GetPerfMonitor()->SetPageUrl(pageUrl);
496 // The page contains only page url but not the page name
497 PerfMonitor::GetPerfMonitor()->SetPageName("");
498 PerfMonitor::GetPerfMonitor()->ReportPageShowMsg(pageUrl, bundleName, "");
499 }
500 }
501
MarkDirtyOverlay()502 void PagePattern::MarkDirtyOverlay()
503 {
504 CHECK_NULL_VOID(overlayManager_);
505 overlayManager_->MarkDirtyOverlay();
506 }
507 } // namespace OHOS::Ace::NG
508