1 /*
2 * Copyright (c) 2022-2023 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/stage_manager.h"
17
18 #include <unordered_map>
19
20 #include "base/geometry/ng/size_t.h"
21 #include "base/log/ace_checker.h"
22 #include "base/log/ace_performance_check.h"
23 #include "base/perfmonitor/perf_monitor.h"
24 #include "base/perfmonitor/perf_constants.h"
25 #include "base/memory/referenced.h"
26 #include "base/utils/time_util.h"
27 #include "base/utils/utils.h"
28 #include "core/animation/page_transition_common.h"
29 #include "core/common/container.h"
30 #include "core/components/common/layout/constants.h"
31 #include "core/components_ng/base/frame_node.h"
32 #include "core/components_ng/base/ui_node.h"
33 #include "core/components_ng/manager/shared_overlay/shared_overlay_manager.h"
34 #include "core/components_ng/pattern/overlay/overlay_manager.h"
35 #include "core/components_ng/pattern/stage/page_pattern.h"
36 #include "core/components_ng/pattern/stage/stage_pattern.h"
37 #include "core/components_ng/property/property.h"
38 #include "core/components_v2/inspector/inspector_constants.h"
39 #include "core/pipeline_ng/pipeline_context.h"
40 #include "core/pipeline_ng/ui_task_scheduler.h"
41
42 namespace OHOS::Ace::NG {
43
44 namespace {
FirePageTransition(const RefPtr<FrameNode> & page,PageTransitionType transitionType)45 void FirePageTransition(const RefPtr<FrameNode>& page, PageTransitionType transitionType)
46 {
47 CHECK_NULL_VOID(page);
48 auto pagePattern = page->GetPattern<PagePattern>();
49 CHECK_NULL_VOID(pagePattern);
50 page->GetEventHub<EventHub>()->SetEnabled(false);
51 pagePattern->SetPageInTransition(true);
52 if (transitionType == PageTransitionType::EXIT_PUSH || transitionType == PageTransitionType::EXIT_POP) {
53 pagePattern->TriggerPageTransition(
54 transitionType, [weak = WeakPtr<FrameNode>(page), transitionType, instanceId = Container::CurrentId()]() {
55 ContainerScope scope(instanceId);
56 LOGI("pageTransition exit finish");
57 auto context = PipelineContext::GetCurrentContext();
58 CHECK_NULL_VOID(context);
59 auto taskExecutor = context->GetTaskExecutor();
60 CHECK_NULL_VOID(taskExecutor);
61 taskExecutor->PostSyncTask(
62 [weak, weakContext = WeakPtr<PipelineContext>(context), transitionType]() {
63 auto page = weak.Upgrade();
64 CHECK_NULL_VOID(page);
65 auto context = weakContext.Upgrade();
66 CHECK_NULL_VOID(context);
67 auto pageFocusHub = page->GetFocusHub();
68 CHECK_NULL_VOID(pageFocusHub);
69 pageFocusHub->SetParentFocusable(false);
70 pageFocusHub->LostFocus();
71 if (transitionType == PageTransitionType::EXIT_POP && page->GetParent()) {
72 auto stageNode = page->GetParent();
73 stageNode->RemoveChild(page);
74 stageNode->RebuildRenderContextTree();
75 context->RequestFrame();
76 return;
77 }
78 page->GetEventHub<EventHub>()->SetEnabled(true);
79 auto pattern = page->GetPattern<PagePattern>();
80 CHECK_NULL_VOID(pattern);
81 pattern->SetPageInTransition(false);
82 pattern->ProcessHideState();
83 context->MarkNeedFlushMouseEvent();
84 },
85 TaskExecutor::TaskType::UI);
86 });
87 return;
88 }
89 PerfMonitor::GetPerfMonitor()->Start(PerfConstants::ABILITY_OR_PAGE_SWITCH, PerfActionType::LAST_UP, "NA");
90 pagePattern->TriggerPageTransition(
91 transitionType, [weak = WeakPtr<FrameNode>(page), instanceId = Container::CurrentId()]() {
92 ContainerScope scope(instanceId);
93 PerfMonitor::GetPerfMonitor()->End(PerfConstants::ABILITY_OR_PAGE_SWITCH, false);
94 LOGI("pageTransition in finish");
95 auto page = weak.Upgrade();
96 CHECK_NULL_VOID(page);
97 page->GetEventHub<EventHub>()->SetEnabled(true);
98 auto pattern = page->GetPattern<PagePattern>();
99 CHECK_NULL_VOID(pattern);
100 pattern->SetPageInTransition(false);
101
102 auto pageFocusHub = page->GetFocusHub();
103 CHECK_NULL_VOID(pageFocusHub);
104 pageFocusHub->SetParentFocusable(true);
105 pageFocusHub->RequestFocusWithDefaultFocusFirstly();
106 auto context = PipelineContext::GetCurrentContext();
107 CHECK_NULL_VOID(context);
108 context->MarkNeedFlushMouseEvent();
109 });
110 }
111 } // namespace
112
StartTransition(const RefPtr<FrameNode> & srcPage,const RefPtr<FrameNode> & destPage,RouteType type)113 void StageManager::StartTransition(const RefPtr<FrameNode>& srcPage, const RefPtr<FrameNode>& destPage, RouteType type)
114 {
115 auto pipeline = PipelineContext::GetCurrentContext();
116 CHECK_NULL_VOID(pipeline);
117 auto sharedManager = pipeline->GetSharedOverlayManager();
118 CHECK_NULL_VOID(sharedManager);
119 sharedManager->StartSharedTransition(srcPage, destPage);
120 srcPageNode_ = srcPage;
121 destPageNode_ = destPage;
122 if (type == RouteType::PUSH) {
123 FirePageTransition(srcPage, PageTransitionType::EXIT_PUSH);
124 FirePageTransition(destPage, PageTransitionType::ENTER_PUSH);
125 } else if (type == RouteType::POP) {
126 FirePageTransition(srcPage, PageTransitionType::EXIT_POP);
127 FirePageTransition(destPage, PageTransitionType::ENTER_POP);
128 }
129 }
130
StageManager(const RefPtr<FrameNode> & stage)131 StageManager::StageManager(const RefPtr<FrameNode>& stage) : stageNode_(stage)
132 {
133 CHECK_NULL_VOID(stageNode_);
134 stagePattern_ = DynamicCast<StagePattern>(stageNode_->GetPattern());
135 }
136
StopPageTransition()137 void StageManager::StopPageTransition()
138 {
139 auto srcNode = srcPageNode_.Upgrade();
140 if (srcNode) {
141 auto pattern = srcNode->GetPattern<PagePattern>();
142 pattern->StopPageTransition();
143 srcPageNode_ = nullptr;
144 }
145 auto destNode = destPageNode_.Upgrade();
146 if (destNode) {
147 auto pattern = destNode->GetPattern<PagePattern>();
148 pattern->StopPageTransition();
149 destPageNode_ = nullptr;
150 }
151 }
152
PushPage(const RefPtr<FrameNode> & node,bool needHideLast,bool needTransition)153 bool StageManager::PushPage(const RefPtr<FrameNode>& node, bool needHideLast, bool needTransition)
154 {
155 CHECK_NULL_RETURN(stageNode_, false);
156 CHECK_NULL_RETURN(node, false);
157 int64_t startTime = GetSysTimestamp();
158 auto pipeline = AceType::DynamicCast<NG::PipelineContext>(PipelineBase::GetCurrentContext());
159 CHECK_NULL_RETURN(pipeline, false);
160 StopPageTransition();
161
162 const auto& children = stageNode_->GetChildren();
163 RefPtr<FrameNode> outPageNode;
164 needTransition &= !children.empty();
165 if (needTransition) {
166 pipeline->FlushPipelineImmediately();
167 }
168 if (!children.empty() && needHideLast) {
169 FirePageHide(children.back(), needTransition ? PageTransitionType::EXIT_PUSH : PageTransitionType::NONE);
170 outPageNode = AceType::DynamicCast<FrameNode>(children.back());
171 }
172 auto rect = stageNode_->GetGeometryNode()->GetFrameRect();
173 rect.SetOffset({});
174 node->GetRenderContext()->SyncGeometryProperties(rect);
175 // mount to parent and mark build render tree.
176 node->MountToParent(stageNode_);
177 // then build the total child.
178 node->Build();
179 stageNode_->RebuildRenderContextTree();
180 FirePageShow(node, needTransition ? PageTransitionType::ENTER_PUSH : PageTransitionType::NONE);
181
182 auto pagePattern = node->GetPattern<PagePattern>();
183 CHECK_NULL_RETURN(pagePattern, false);
184 stagePattern_->currentPageIndex_ = pagePattern->GetPageInfo()->GetPageId();
185 if (AceChecker::IsPerformanceCheckEnabled()) {
186 // After completing layout tasks at all nodes on the page, perform performance testing and management
187 pipeline->AddAfterLayoutTask([weakStage = WeakClaim(this), weakNode = WeakPtr<FrameNode>(node), startTime]() {
188 auto stage = weakStage.Upgrade();
189 CHECK_NULL_VOID(stage);
190 auto pageNode = weakNode.Upgrade();
191 int64_t endTime = GetSysTimestamp();
192 stage->PerformanceCheck(pageNode, endTime - startTime);
193 });
194 }
195 if (needTransition) {
196 pipeline->AddAfterLayoutTask([weakStage = WeakClaim(this), weakIn = WeakPtr<FrameNode>(node),
197 weakOut = WeakPtr<FrameNode>(outPageNode)]() {
198 auto stage = weakStage.Upgrade();
199 CHECK_NULL_VOID(stage);
200 auto inPageNode = weakIn.Upgrade();
201 auto outPageNode = weakOut.Upgrade();
202 stage->StartTransition(outPageNode, inPageNode, RouteType::PUSH);
203 });
204 }
205
206 // flush layout task.
207 if (!stageNode_->GetGeometryNode()->GetMarginFrameSize().IsPositive()) {
208 // in first load case, wait for window size.
209 LOGI("waiting for window size");
210 return true;
211 }
212 stageNode_->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
213 node->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
214
215 return true;
216 }
217
PerformanceCheck(const RefPtr<FrameNode> & pageNode,int64_t vsyncTimeout)218 void StageManager::PerformanceCheck(const RefPtr<FrameNode>& pageNode, int64_t vsyncTimeout)
219 {
220 CHECK_NULL_VOID_NOLOG(pageNode);
221 PerformanceCheckNodeMap nodeMap;
222 pageNode->GetPerformanceCheckData(nodeMap);
223 AceScopedPerformanceCheck::RecordPerformanceCheckData(nodeMap, vsyncTimeout);
224 }
225
PopPage(bool needShowNext,bool needTransition)226 bool StageManager::PopPage(bool needShowNext, bool needTransition)
227 {
228 auto pipeline = PipelineContext::GetCurrentContext();
229 CHECK_NULL_RETURN(pipeline, false);
230 CHECK_NULL_RETURN(stageNode_, false);
231 StopPageTransition();
232 const auto& children = stageNode_->GetChildren();
233 if (children.empty()) {
234 LOGE("fail to pop page due to children is null");
235 return false;
236 }
237 auto pageNode = children.back();
238 const size_t transitionPageSize = 2;
239 needTransition &= (children.size() >= transitionPageSize);
240 if (needTransition) {
241 pipeline->FlushPipelineImmediately();
242 }
243 FirePageHide(pageNode, needTransition ? PageTransitionType::EXIT_POP : PageTransitionType::NONE);
244
245 RefPtr<FrameNode> inPageNode;
246 if (needShowNext && children.size() >= transitionPageSize) {
247 auto newPageNode = *(++children.rbegin());
248 FirePageShow(newPageNode, needTransition ? PageTransitionType::ENTER_POP : PageTransitionType::NONE);
249 inPageNode = AceType::DynamicCast<FrameNode>(newPageNode);
250 }
251
252 auto outPageNode = AceType::DynamicCast<FrameNode>(pageNode);
253 if (needTransition) {
254 StartTransition(outPageNode, inPageNode, RouteType::POP);
255 inPageNode->OnAccessibilityEvent(AccessibilityEventType::CHANGE);
256 return true;
257 }
258 stageNode_->RemoveChild(pageNode);
259 pageNode->SetChildrenInDestroying();
260 stageNode_->RebuildRenderContextTree();
261 pipeline->RequestFrame();
262 return true;
263 }
264
PopPageToIndex(int32_t index,bool needShowNext,bool needTransition)265 bool StageManager::PopPageToIndex(int32_t index, bool needShowNext, bool needTransition)
266 {
267 auto pipeline = PipelineContext::GetCurrentContext();
268 CHECK_NULL_RETURN(pipeline, false);
269 CHECK_NULL_RETURN(stageNode_, false);
270 StopPageTransition();
271 const auto& children = stageNode_->GetChildren();
272 if (children.empty()) {
273 LOGE("fail to pop page due to children is null");
274 return false;
275 }
276 int32_t popSize = static_cast<int32_t>(children.size()) - index - 1;
277 if (popSize < 0) {
278 LOGE("fail to pop page due to index is out of range");
279 return false;
280 }
281 if (popSize == 0) {
282 LOGD("already here");
283 return true;
284 }
285
286 if (needTransition) {
287 pipeline->FlushPipelineImmediately();
288 }
289 bool firstPageTransition = true;
290 auto outPageNode = AceType::DynamicCast<FrameNode>(children.back());
291 auto iter = children.rbegin();
292 for (int32_t current = 0; current < popSize; ++current) {
293 auto pageNode = *iter;
294 FirePageHide(
295 pageNode, firstPageTransition && needTransition ? PageTransitionType::EXIT_POP : PageTransitionType::NONE);
296 firstPageTransition = false;
297 ++iter;
298 }
299
300 RefPtr<FrameNode> inPageNode;
301 if (needShowNext) {
302 const auto& newPageNode = *iter;
303 FirePageShow(newPageNode, needTransition ? PageTransitionType::ENTER_POP : PageTransitionType::NONE);
304 inPageNode = AceType::DynamicCast<FrameNode>(newPageNode);
305 }
306
307 if (needTransition) {
308 // from the penultimate node, (popSize - 1) nodes are deleted.
309 // the last node will be deleted after pageTransition
310 LOGI("PopPageToIndex, before pageTransition, to index:%{public}d, children size:%{public}zu, "
311 "stage children size:%{public}zu",
312 index, children.size(), stageNode_->GetChildren().size());
313 for (int32_t current = 1; current < popSize; ++current) {
314 auto pageNode = *(++children.rbegin());
315 stageNode_->RemoveChild(pageNode);
316 }
317 stageNode_->RebuildRenderContextTree();
318 StartTransition(outPageNode, inPageNode, RouteType::POP);
319 return true;
320 }
321 for (int32_t current = 0; current < popSize; ++current) {
322 auto pageNode = children.back();
323 stageNode_->RemoveChild(pageNode);
324 }
325 stageNode_->RebuildRenderContextTree();
326 pipeline->RequestFrame();
327 return true;
328 }
329
CleanPageStack()330 bool StageManager::CleanPageStack()
331 {
332 auto pipeline = PipelineContext::GetCurrentContext();
333 CHECK_NULL_RETURN(pipeline, false);
334 CHECK_NULL_RETURN(stageNode_, false);
335 const auto& children = stageNode_->GetChildren();
336 if (children.size() <= 1) {
337 LOGE("fail to clean page stack due to children size is illegal");
338 return false;
339 }
340 auto popSize = static_cast<int32_t>(children.size() - 1);
341 for (int32_t count = 1; count <= popSize; ++count) {
342 auto pageNode = children.front();
343 // mark pageNode child as destroying
344 pageNode->SetChildrenInDestroying();
345 stageNode_->RemoveChild(pageNode);
346 }
347 stageNode_->RebuildRenderContextTree();
348 pipeline->RequestFrame();
349 return true;
350 }
351
MovePageToFront(const RefPtr<FrameNode> & node,bool needHideLast,bool needTransition)352 bool StageManager::MovePageToFront(const RefPtr<FrameNode>& node, bool needHideLast, bool needTransition)
353 {
354 auto pipeline = PipelineContext::GetCurrentContext();
355 CHECK_NULL_RETURN(pipeline, false);
356 CHECK_NULL_RETURN(stageNode_, false);
357 StopPageTransition();
358 const auto& children = stageNode_->GetChildren();
359 if (children.empty()) {
360 LOGE("child is empty");
361 return false;
362 }
363 const auto& lastPage = children.back();
364 if (lastPage == node) {
365 LOGD("page already on the top");
366 return true;
367 }
368 if (needTransition) {
369 pipeline->FlushPipelineImmediately();
370 }
371 if (needHideLast) {
372 FirePageHide(lastPage, needTransition ? PageTransitionType::EXIT_PUSH : PageTransitionType::NONE);
373 }
374 node->MovePosition(static_cast<int32_t>(stageNode_->GetChildren().size() - 1));
375 node->GetRenderContext()->ResetPageTransitionEffect();
376 FirePageShow(node, needTransition ? PageTransitionType::ENTER_PUSH : PageTransitionType::NONE);
377
378 stageNode_->RebuildRenderContextTree();
379 if (needTransition) {
380 auto outPageNode = AceType::DynamicCast<FrameNode>(lastPage);
381 StartTransition(outPageNode, node, RouteType::PUSH);
382 }
383 pipeline->RequestFrame();
384 return true;
385 }
386
FirePageHide(const RefPtr<UINode> & node,PageTransitionType transitionType)387 void StageManager::FirePageHide(const RefPtr<UINode>& node, PageTransitionType transitionType)
388 {
389 auto pageNode = DynamicCast<FrameNode>(node);
390 CHECK_NULL_VOID(pageNode);
391 auto pagePattern = pageNode->GetPattern<PagePattern>();
392 CHECK_NULL_VOID(pagePattern);
393 pagePattern->OnHide();
394 if (transitionType == PageTransitionType::NONE) {
395 // If there is a page transition, this function should execute after page transition,
396 // otherwise the page will not be visible
397 pagePattern->ProcessHideState();
398 }
399
400 auto pageFocusHub = pageNode->GetFocusHub();
401 CHECK_NULL_VOID(pageFocusHub);
402 pageFocusHub->SetParentFocusable(false);
403 pageFocusHub->LostFocus();
404
405 auto context = PipelineContext::GetCurrentContext();
406 CHECK_NULL_VOID_NOLOG(context);
407 context->MarkNeedFlushMouseEvent();
408 }
409
FirePageShow(const RefPtr<UINode> & node,PageTransitionType transitionType)410 void StageManager::FirePageShow(const RefPtr<UINode>& node, PageTransitionType transitionType)
411 {
412 auto pageNode = DynamicCast<FrameNode>(node);
413 CHECK_NULL_VOID(pageNode);
414 auto layoutProperty = pageNode->GetLayoutProperty();
415 auto pageFocusHub = pageNode->GetFocusHub();
416 CHECK_NULL_VOID(pageFocusHub);
417 pageFocusHub->SetParentFocusable(true);
418 pageFocusHub->RequestFocusWithDefaultFocusFirstly();
419
420 auto pagePattern = pageNode->GetPattern<PagePattern>();
421 CHECK_NULL_VOID(pagePattern);
422 pagePattern->OnShow();
423 // With or without a page transition, we need to make the coming page visible first
424 pagePattern->ProcessShowState();
425
426 auto context = PipelineContext::GetCurrentContext();
427 CHECK_NULL_VOID_NOLOG(context);
428 context->MarkNeedFlushMouseEvent();
429 #ifdef UICAST_COMPONENT_SUPPORTED
430 do {
431 auto container = Container::Current();
432 CHECK_NULL_BREAK(container);
433 auto distributedUI = container->GetDistributedUI();
434 CHECK_NULL_BREAK(distributedUI);
435 distributedUI->OnPageChanged(node->GetPageId());
436 } while (false);
437 #endif
438 }
439
GetLastPage()440 RefPtr<FrameNode> StageManager::GetLastPage()
441 {
442 CHECK_NULL_RETURN(stageNode_, nullptr);
443 const auto& children = stageNode_->GetChildren();
444 if (children.empty()) {
445 LOGE("fail to return page due to children is null");
446 return nullptr;
447 }
448 return DynamicCast<FrameNode>(children.back());
449 }
450
GetPageById(int32_t pageId)451 RefPtr<FrameNode> StageManager::GetPageById(int32_t pageId)
452 {
453 CHECK_NULL_RETURN(stageNode_, nullptr);
454 const auto& children = stageNode_->GetChildren();
455 for (const auto& child : children) {
456 if (child->GetPageId() == pageId) {
457 return DynamicCast<FrameNode>(child);
458 }
459 }
460 LOGD("UITree page not found. %{public}d", pageId);
461 return nullptr;
462 }
463
ReloadStage()464 void StageManager::ReloadStage()
465 {
466 CHECK_NULL_VOID(stageNode_);
467 const auto& children = stageNode_->GetChildren();
468 for (const auto& child : children) {
469 auto frameNode = DynamicCast<FrameNode>(child);
470 if (!frameNode) {
471 continue;
472 }
473 auto pagePattern = frameNode->GetPattern<PagePattern>();
474 if (!pagePattern) {
475 continue;
476 }
477 pagePattern->ReloadPage();
478 }
479 }
480
481 } // namespace OHOS::Ace::NG
482