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