1 /*
2 * Copyright (c) 2025 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 "frameworks/bridge/declarative_frontend/ng/page_router_manager.h"
17 #include "base/ressched/ressched_report.h"
18 #include "base/perfmonitor/perf_monitor.h"
19 #include "bridge/declarative_frontend/engine/jsi/jsi_declarative_engine.h"
20 #include "core/components_ng/base/frame_node.h"
21 #include "core/components_ng/base/view_advanced_register.h"
22 #include "core/components_ng/pattern/stage/page_node.h"
23 #include "core/components_ng/pattern/stage/page_pattern.h"
24 #include "core/components_ng/pattern/stage/stage_manager.h"
25 #include "core/components_v2/inspector/inspector_constants.h"
26 #include "core/pipeline/base/element_register.h"
27 #include "core/pipeline_ng/pipeline_context.h"
28
29 namespace OHOS::Ace::NG {
30 namespace {
31 constexpr int32_t INVALID_PAGE_INDEX = -1;
32 constexpr int32_t MAX_ROUTER_STACK_SIZE = 32;
33 } // namespace
34
PushExtender(const RouterPageInfo & target)35 RefPtr<FrameNode> PageRouterManager::PushExtender(const RouterPageInfo& target)
36 {
37 CHECK_RUN_ON(JS);
38 if (inRouterOpt_) {
39 auto context = PipelineContext::GetCurrentContext();
40 CHECK_NULL_RETURN(context, nullptr);
41 context->PostAsyncEvent(
42 [weak = WeakClaim(this), target]() {
43 auto router = weak.Upgrade();
44 CHECK_NULL_VOID(router);
45 router->Push(target);
46 },
47 "ArkUIPageRouterPush", TaskExecutor::TaskType::JS);
48 return nullptr;
49 }
50 RouterOptScope scope(this);
51 if (target.url.empty()) {
52 TAG_LOGE(AceLogTag::ACE_ROUTER, "push url is empty");
53 return nullptr;
54 }
55 auto context = PipelineContext::GetCurrentContext();
56 CHECK_NULL_RETURN(context, nullptr);
57 auto stageManager = context->GetStageManager();
58 CHECK_NULL_RETURN(stageManager, nullptr);
59 if (GetStackSize() >= MAX_ROUTER_STACK_SIZE && !stageManager->GetForceSplitEnable()) {
60 TAG_LOGW(AceLogTag::ACE_ROUTER, "StartPush exceeds maxStackSize.");
61 if (target.errorCallback != nullptr) {
62 target.errorCallback("The pages are pushed too much.", ERROR_CODE_PAGE_STACK_FULL);
63 }
64 return nullptr;
65 }
66 RouterPageInfo info = target;
67 info.path = info.url + ".js";
68 if (info.path.empty()) {
69 TAG_LOGW(AceLogTag::ACE_ROUTER, "empty path found in StartPush with url: %{public}s", info.url.c_str());
70 if (info.errorCallback != nullptr) {
71 info.errorCallback("The uri of router is not exist.", ERROR_CODE_URI_ERROR);
72 }
73 return nullptr;
74 }
75
76 CleanPageOverlay();
77 UpdateSrcPage();
78
79 if (info.routerMode == RouterMode::SINGLE) {
80 auto pageInfo = FindPageInStack(info.url);
81 if (pageInfo.second) {
82 // find page in stack, move postion and update params.
83 auto pagePattern = pageInfo.second->GetPattern<PagePattern>();
84 if (pagePattern) {
85 pagePattern->FireOnNewParam(info.params);
86 }
87 MovePageToFront(pageInfo.first, pageInfo.second, info, true);
88 return pagePattern->GetHost();
89 }
90 auto index = FindPageInRestoreStack(info.url);
91 if (index != INVALID_PAGE_INDEX) {
92 // find page in restore page, create page, move position and update params.
93 RestorePageWithTarget(index, false, info, RestorePageDestination::TOP);
94 return pageInfo.second;
95 }
96 }
97 auto loadPageSuccess = LoadPageExtender(GenerateNextPageId(), info, true, true, true);
98 if (!loadPageSuccess) {
99 return nullptr;
100 }
101 auto pageNode = pageRouterStack_.back().Upgrade();
102 return pageNode;
103 }
104
ReplaceExtender(const RouterPageInfo & target,std::function<void ()> && enterFinishCallback)105 RefPtr<FrameNode> PageRouterManager::ReplaceExtender(const RouterPageInfo& target,
106 std::function<void()>&& enterFinishCallback)
107 {
108 CHECK_RUN_ON(JS);
109 if (inRouterOpt_) {
110 auto context = PipelineContext::GetCurrentContext();
111 CHECK_NULL_RETURN(context, nullptr);
112 context->PostAsyncEvent(
113 [weak = WeakClaim(this), target]() {
114 auto router = weak.Upgrade();
115 CHECK_NULL_VOID(router);
116 router->Replace(target);
117 },
118 "ArkUIPageRouterReplace", TaskExecutor::TaskType::JS);
119 return nullptr;
120 }
121 RouterOptScope scope(this);
122 CleanPageOverlay();
123 if (target.url.empty()) {
124 return nullptr;
125 }
126
127 RouterPageInfo info = target;
128 info.path = info.url + ".js";
129 if (info.path.empty()) {
130 TAG_LOGW(AceLogTag::ACE_ROUTER, "empty path found in StartReplace with url: %{public}s", info.url.c_str());
131 if (info.errorCallback != nullptr) {
132 info.errorCallback("The uri of router is not exist.", ERROR_CODE_URI_ERROR_LITE);
133 }
134 return nullptr;
135 }
136 UpdateSrcPage();
137
138 auto pipelineContext = PipelineContext::GetCurrentContext();
139 CHECK_NULL_RETURN(pipelineContext, nullptr);
140 #if defined(ENABLE_SPLIT_MODE)
141 auto stageManager = pipelineContext->GetStageManager();
142 CHECK_NULL_RETURN(stageManager, nullptr);
143 #endif
144 TAG_LOGI(AceLogTag::ACE_ROUTER,
145 "router replace in new lifecycle(API version > 11), replace mode: %{public}d, url: %{public}s",
146 static_cast<int32_t>(info.routerMode), info.url.c_str());
147 auto popNode = GetCurrentPageNode();
148 int32_t popIndex = static_cast<int32_t>(pageRouterStack_.size()) - 1;
149 bool findPage = false;
150 if (info.routerMode == RouterMode::SINGLE) {
151 auto pageInfo = FindPageInStack(info.url);
152 // haven't find page by named route's name. Try again with its page path.
153 if (pageInfo.second == nullptr && info.isNamedRouterMode) {
154 std::string pagePath = Framework::JsiDeclarativeEngine::GetPagePath(info.url);
155 pageInfo = FindPageInStack(pagePath);
156 }
157 auto replacePageNode = pageInfo.second;
158 if (pageInfo.first == popIndex) {
159 // replace top self in SINGLE mode, do nothing.
160 CHECK_NULL_RETURN(replacePageNode, nullptr);
161 auto pagePattern = pageInfo.second->GetPattern<PagePattern>();
162 if (pagePattern) {
163 pagePattern->FireOnNewParam(info.params);
164 }
165 return replacePageNode;
166 }
167 if (replacePageNode) {
168 // find page in stack, move position and update params.
169 #if defined(ENABLE_SPLIT_MODE)
170 stageManager->SetIsNewPageReplacing(true);
171 #endif
172 MovePageToFront(pageInfo.first, replacePageNode, info, false, true, false);
173 #if defined(ENABLE_SPLIT_MODE)
174 stageManager->SetIsNewPageReplacing(false);
175 #endif
176 popIndex = popIndex - 1;
177 findPage = true;
178 auto pagePattern = replacePageNode->GetPattern<PagePattern>();
179 if (pagePattern) {
180 pagePattern->FireOnNewParam(info.params);
181 }
182 } else {
183 auto index = FindPageInRestoreStack(info.url);
184 if (index != INVALID_PAGE_INDEX) {
185 // find page in restore page, create page, move position and update params.
186 RestorePageWithTarget(index, false, info, RestorePageDestination::BELLOW_TOP, false);
187 return replacePageNode;
188 }
189 }
190 }
191 RefPtr<FrameNode> pageNode = nullptr;
192 if (!findPage) {
193 isNewPageReplacing_ = true;
194 #if defined(ENABLE_SPLIT_MODE)
195 stageManager->SetIsNewPageReplacing(true);
196 #endif
197 bool loadPageSuccess = LoadPageExtender(GenerateNextPageId(), info, false, false);
198 if (loadPageSuccess) {
199 pageNode = pageRouterStack_.back().Upgrade();
200 }
201 #if defined(ENABLE_SPLIT_MODE)
202 stageManager->SetIsNewPageReplacing(false);
203 #endif
204 isNewPageReplacing_ = false;
205 }
206 if (popIndex < 0 || popNode == GetCurrentPageNode()) {
207 return pageNode;
208 }
209 CHECK_NULL_RETURN(popNode, nullptr);
210 auto pagePattern = popNode->GetPattern<PagePattern>();
211 CHECK_NULL_RETURN(pagePattern, nullptr);
212 pagePattern->SetOnNodeDisposeCallback(std::move(enterFinishCallback));
213 auto iter = pageRouterStack_.begin();
214 std::advance(iter, popIndex);
215 auto lastIter = pageRouterStack_.erase(iter);
216 pageRouterStack_.emplace_back(WeakPtr<FrameNode>(AceType::DynamicCast<FrameNode>(popNode)));
217 popNode->MovePosition(GetLastPageIndex());
218 for (auto iter = lastIter; iter != pageRouterStack_.end(); ++iter, ++popIndex) {
219 auto page = iter->Upgrade();
220 if (!page) {
221 continue;
222 }
223 if (page == popNode) {
224 // do not change index of page that will be replaced.
225 continue;
226 }
227 auto pagePattern = page->GetPattern<NG::PagePattern>();
228 pagePattern->GetPageInfo()->SetPageIndex(popIndex + 1);
229 }
230 #if defined(ENABLE_SPLIT_MODE)
231 stageManager->SetIsNewPageReplacing(true);
232 #endif
233 PopPage("", false, false);
234 #if defined(ENABLE_SPLIT_MODE)
235 stageManager->SetIsNewPageReplacing(false);
236 #endif
237 return pageNode;
238 }
239
RunPageExtender(const RouterPageInfo & target)240 RefPtr<FrameNode> PageRouterManager::RunPageExtender(
241 const RouterPageInfo& target)
242 {
243 PerfMonitor::GetPerfMonitor()->SetAppStartStatus();
244 ACE_SCOPED_TRACE("PageRouterManager::RunPage");
245 CHECK_RUN_ON(JS);
246 RouterPageInfo info = target;
247 info.path = info.url + ".js";
248 auto loadPageSuccess = LoadPageExtender(GenerateNextPageId(), info);
249 if (!loadPageSuccess) {
250 return nullptr;
251 }
252 auto pageNode = pageRouterStack_.back().Upgrade();
253 return pageNode;
254 }
255
LoadPageExtender(int32_t pageId,const RouterPageInfo & target,bool needHideLast,bool needTransition,bool)256 bool PageRouterManager::LoadPageExtender(
257 int32_t pageId, const RouterPageInfo& target, bool needHideLast, bool needTransition, bool /*isPush*/)
258 {
259 ACE_SCOPED_TRACE_COMMERCIAL("load page: %s(id:%d)", target.url.c_str(), pageId);
260 CHECK_RUN_ON(JS);
261 auto pageNode = CreatePageExtender(pageId, target);
262
263 if (!pageNode) {
264 TAG_LOGE(AceLogTag::ACE_ROUTER, "failed to create page in LoadPage");
265 return false;
266 }
267
268 pageRouterStack_.emplace_back(pageNode);
269 if (!OnPageReady(pageNode, needHideLast, needTransition)) {
270 pageRouterStack_.pop_back();
271 TAG_LOGW(AceLogTag::ACE_ROUTER, "LoadPage OnPageReady Failed");
272 return false;
273 }
274 AccessibilityEventType type = AccessibilityEventType::CHANGE;
275 pageNode->OnAccessibilityEvent(type);
276 TAG_LOGI(AceLogTag::ACE_ROUTER, "LoadPage Success");
277 return true;
278 }
279
CreatePageExtender(int32_t pageId,const RouterPageInfo & target)280 RefPtr<FrameNode> PageRouterManager::CreatePageExtender(int32_t pageId, const RouterPageInfo& target)
281 {
282 ACE_SCOPED_TRACE("PageRouterManager::CreatePage");
283 CHECK_RUN_ON(JS);
284 TAG_LOGI(AceLogTag::ACE_ROUTER,
285 "Page router manager is creating page[%{public}d]: url: %{public}s path: "
286 "%{public}s, recoverable: %{public}s, namedRouter: %{public}s",
287 pageId, target.url.c_str(), target.path.c_str(), (target.recoverable ? "yes" : "no"),
288 (target.isNamedRouterMode ? "yes" : "no"));
289 auto entryPageInfo = AceType::MakeRefPtr<EntryPageInfo>(
290 pageId, target.url, target.path, target.params, target.recoverable, target.isNamedRouterMode);
291 auto pagePattern = ViewAdvancedRegister::GetInstance()->CreatePagePattern(entryPageInfo);
292 std::unordered_map<std::string, std::string> reportData { { "pageUrl", target.url } };
293 ResSchedReportScope reportScope("push_page", reportData);
294 auto pageNode = PageNode::CreatePageNode(ElementRegister::GetInstance()->MakeUniqueId(), pagePattern);
295 pageNode->SetHostPageId(pageId);
296 // !!! must push_back first for UpdateRootComponent
297 pageRouterStack_.emplace_back(pageNode);
298
299 // record full path info of every pageNode
300 auto pageInfo = pagePattern->GetPageInfo();
301 if (!pageInfo) {
302 pageRouterStack_.pop_back();
303 return nullptr;
304 }
305 auto keyInfo = target.url;
306 if (keyInfo.empty() && manifestParser_) {
307 auto router = manifestParser_->GetRouter();
308 if (router) {
309 keyInfo = router->GetEntry("");
310 }
311 }
312 #if !defined(PREVIEW)
313 if (keyInfo.substr(0, strlen(BUNDLE_TAG)) == BUNDLE_TAG) {
314 // deal with @bundle url
315 // @bundle format: @bundle:bundleName/moduleName/pagePath/fileName(without file extension)
316 // @bundle example: @bundle:com.example.applicationHsp/hsp/ets/mylib/pages/Index
317 // only moduleName and pagePath/fileName is needed: hspmylib/pages/Index
318 size_t bundleEndPos = keyInfo.find('/');
319 size_t moduleStartPos = bundleEndPos + 1;
320 size_t moduleEndPos = keyInfo.find('/', moduleStartPos);
321 std::string moduleName = keyInfo.substr(moduleStartPos, moduleEndPos - moduleStartPos);
322 size_t pageInfoStartPos = keyInfo.find('/', moduleEndPos + 1);
323 keyInfo = keyInfo.substr(pageInfoStartPos + 1);
324 keyInfo = moduleName + keyInfo;
325 }
326 #endif
327 SetPageInfoRouteName(entryPageInfo);
328 auto pagePath = Framework::JsiDeclarativeEngine::GetFullPathInfo(keyInfo);
329 if (pagePath.empty()) {
330 auto container = Container::Current();
331 if (!container) {
332 pageRouterStack_.pop_back();
333 return nullptr;
334 }
335 auto moduleName = container->GetModuleName();
336 keyInfo = moduleName + keyInfo;
337 pagePath = Framework::JsiDeclarativeEngine::GetFullPathInfo(keyInfo);
338 }
339 pageInfo->SetFullPath(pagePath);
340
341 #if defined(PREVIEW)
342 if (!isComponentPreview_()) {
343 #endif
344 if (target.isNamedRouterMode) {
345 if (manifestParser_) {
346 manifestParser_->SetPagePath(target.url);
347 } else {
348 TAG_LOGE(AceLogTag::ACE_ROUTER, "set routeName in manifest failed, manifestParser is null!");
349 }
350 }
351
352 if (target.errorCallback != nullptr) {
353 target.errorCallback("", ERROR_CODE_NO_ERROR);
354 }
355 #if defined(PREVIEW)
356 }
357 #endif
358
359 pageRouterStack_.pop_back();
360 return pageNode;
361 }
362 } // namespace OHOS::Ace::NG