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