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