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