1 /*
2 * Copyright (c) 2024 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 "bridge/cj_frontend/frontend/cj_page_router.h"
17
18 #include "bridge/cj_frontend/frontend/cj_frontend_abstract.h"
19 #include "bridge/cj_frontend/frontend/cj_page_loader.h"
20 #include "bridge/cj_frontend/runtime/cj_runtime_delegate.h"
21 #include "bridge/common/accessibility/accessibility_node_manager.h"
22 #include "bridge/declarative_frontend/view_stack_processor.h"
23
24 namespace OHOS::Ace::Framework {
SetCjPageCallbackClassic(RefPtr<Framework::JsAcePage> page,NativeView * view)25 void SetCjPageCallbackClassic(RefPtr<Framework::JsAcePage> page, NativeView* view)
26 {
27 CHECK_NULL_VOID(view);
28 wptr<NativeView> weak = view;
29 CHECK_NULL_VOID(page);
30 page->SetDeclarativeOnPageAppearCallback([weak]() {
31 auto view = weak.promote();
32 if (view) {
33 view->FireOnShow();
34 }
35 });
36 page->SetDeclarativeOnPageDisAppearCallback([weak]() {
37 auto view = weak.promote();
38 if (view) {
39 view->FireOnHide();
40 }
41 });
42 page->SetDeclarativeOnBackPressCallback([weak]() {
43 auto view = weak.promote();
44 if (view) {
45 return view->FireOnBackPress();
46 }
47 return false;
48 });
49 page->SetDeclarativeOnPageRefreshCallback([weak]() {
50 auto view = weak.promote();
51 if (view) {
52 view->MarkNeedUpdate();
53 }
54 });
55 page->SetDeclarativeOnUpdateWithValueParamsCallback([weak](const std::string& params) {
56 auto view = weak.promote();
57 if (view && !params.empty()) {
58 view->ExecuteUpdateWithValueParams(params);
59 }
60 });
61 }
62
LoadNativeViewClassic(NativeView * view)63 bool LoadNativeViewClassic(NativeView* view)
64 {
65 auto currentObj = Container::Current();
66 if (!currentObj) {
67 LOGE("LoadNativeView container is null");
68 return false;
69 }
70
71 auto frontend = AceType::DynamicCast<CJFrontendAbstract>(currentObj->GetFrontend());
72 if (!frontend) {
73 LOGE("LoadNativeView frontend is null or not CJFrontend");
74 return false;
75 }
76 auto router = AceType::DynamicCast<CJPageRouter>(frontend->GetPageRouterManager());
77 if (!router) {
78 LOGE("LoadNativeView router is not CJPageRouter");
79 return false;
80 }
81 auto page = router->GetLoadingPage();
82 auto rootComponent = AceType::DynamicCast<Component>(view->CreateUI());
83 std::list<RefPtr<Component>> stackChildren;
84 stackChildren.emplace_back(rootComponent);
85 auto rootStackComponent = AceType::MakeRefPtr<StackComponent>(
86 Alignment::TOP_LEFT, StackFit::INHERIT, Overflow::OBSERVABLE, stackChildren);
87 rootStackComponent->SetMainStackSize(MainStackSize::MAX);
88 auto rootComposed = AceType::MakeRefPtr<ComposedComponent>("0", "root");
89 rootComposed->SetChild(rootStackComponent);
90 page->SetRootComponent(rootComposed);
91 auto pageTransitionComponent = ViewStackProcessor::GetInstance()->GetPageTransitionComponent();
92 ViewStackProcessor::GetInstance()->ClearPageTransitionComponent();
93 page->SetPageTransition(pageTransitionComponent);
94
95 // We are done, tell to the JSAgePage
96 page->SetPageCreated();
97 SetCjPageCallbackClassic(page, view);
98 return true;
99 }
100
PushLoadingPage(const RefPtr<JsAcePage> & page)101 void CJPageRouter::PushLoadingPage(const RefPtr<JsAcePage>& page)
102 {
103 loadingPage_ = page;
104 }
105
OnShowCurrent()106 void CJPageRouter::OnShowCurrent()
107 {
108 auto topPage = GetTopPage();
109 CHECK_NULL_VOID(topPage);
110 topPage->FireDeclarativeOnPageAppearCallback();
111 }
112
OnHideCurrent()113 void CJPageRouter::OnHideCurrent()
114 {
115 auto topPage = GetTopPage();
116 CHECK_NULL_VOID(topPage);
117 topPage->FireDeclarativeOnPageDisAppearCallback();
118 }
119
AllowPopLastPage()120 bool CJPageRouter::AllowPopLastPage()
121 {
122 return true;
123 }
124
LoadPage(int32_t pageId,const OHOS::Ace::CJPageRouterAbstract::RouterPageInfo & target,const std::string & params,bool isRestore,bool needHideLast,bool needTransition)125 void CJPageRouter::LoadPage(int32_t pageId, const OHOS::Ace::CJPageRouterAbstract::RouterPageInfo& target,
126 const std::string& params, bool isRestore, bool needHideLast, bool needTransition)
127 {
128 LOGI("LoadPage[%{public}d]: %{public}s.", pageId, target.url.c_str());
129 if (pageId < 0) {
130 LOGE("FrontendDelegateDeclarative, invalid page id");
131 return;
132 }
133 auto document = AceType::MakeRefPtr<DOMDocument>(pageId);
134 auto page = AceType::MakeRefPtr<JsAcePage>(pageId, document, target.url, nullptr);
135 page->SetPageParams(params);
136 PushLoadingPage(page);
137 pageRouterStack_.emplace_back(page);
138
139 page->SetFlushCallback([weak = AceType::WeakClaim(this), url = target.url](const RefPtr<JsAcePage>& acePage) {
140 CHECK_NULL_VOID(acePage);
141 auto delegate = weak.Upgrade();
142 CHECK_NULL_VOID(delegate);
143 delegate->FlushPage(acePage, url);
144 });
145 auto frontend = frontend_.Upgrade();
146 CHECK_NULL_VOID(frontend);
147 if (!CJRuntimeDelegate::GetInstance()->LoadAppEntry(target.url)) {
148 LOGE("Run CJ Page fail: %{public}s", target.url.c_str());
149 pageRouterStack_.pop_back();
150 return;
151 }
152 page->FlushCommands();
153 auto pipeline = AceType::DynamicCast<PipelineContext>(frontend->GetPipelineContext());
154 CHECK_NULL_VOID(pipeline);
155 pipeline->FlushFocus();
156 document->HandlePageLoadFinish();
157 }
158
EnableAlertBeforeBackPage(const std::string & message,std::function<void (int32_t)> callback)159 void CJPageRouter::EnableAlertBeforeBackPage(const std::string& message, std::function<void(int32_t)> callback) {}
160
GetStackSize() const161 int32_t CJPageRouter::GetStackSize() const
162 {
163 return pageRouterStack_.size();
164 }
165
GetState(int32_t & index,std::string & name,std::string & path)166 void CJPageRouter::GetState(int32_t& index, std::string& name, std::string& path)
167 {
168 if (pageRouterStack_.empty()) {
169 LOGE("fail to get page state due to stack is null");
170 return;
171 }
172 index = static_cast<int32_t>(pageRouterStack_.size());
173 auto topPage = GetTopPage();
174 CHECK_NULL_VOID(topPage);
175
176 name = topPage->GetUrl();
177 // path is invalid for cj
178 }
179
GetParams() const180 std::string CJPageRouter::GetParams() const
181 {
182 auto topPage = GetTopPage();
183 if (!topPage) {
184 return "";
185 }
186 return topPage->GetPageParams();
187 }
188
GetCurrentPageUrl()189 std::string CJPageRouter::GetCurrentPageUrl()
190 {
191 auto topPage = GetTopPage();
192 if (!topPage) {
193 return "";
194 }
195 return topPage->GetUrl();
196 }
197
StartPush(const OHOS::Ace::CJPageRouterAbstract::RouterPageInfo & target,const std::string & params,OHOS::Ace::CJPageRouterAbstract::RouterMode mode)198 void CJPageRouter::StartPush(const OHOS::Ace::CJPageRouterAbstract::RouterPageInfo& target, const std::string& params,
199 OHOS::Ace::CJPageRouterAbstract::RouterMode mode)
200 {
201 ProcessGuard guard(this);
202 if (target.url.empty()) {
203 LOGE("router.Push uri is empty");
204 return;
205 }
206
207 LoadPage(GenerateNextPageId(), target, params);
208 }
209
StartPop()210 bool CJPageRouter::StartPop()
211 {
212 ProcessGuard guard(this);
213 if (pageRouterStack_.size() <= 1) {
214 // the last page.
215 return false;
216 }
217 auto topNode = pageRouterStack_.back();
218 pageRouterStack_.pop_back();
219 if (!OnPopPage(true, true)) {
220 LOGE("fail to pop page");
221 pageRouterStack_.emplace_back(topNode);
222 return false;
223 }
224 return true;
225 }
226
StartReplace(const OHOS::Ace::CJPageRouterAbstract::RouterPageInfo & target,const std::string & params,OHOS::Ace::CJPageRouterAbstract::RouterMode mode)227 void CJPageRouter::StartReplace(const OHOS::Ace::CJPageRouterAbstract::RouterPageInfo& target,
228 const std::string& params, OHOS::Ace::CJPageRouterAbstract::RouterMode mode)
229 {
230 ProcessGuard guard(this);
231 if (target.url.empty()) {
232 LOGE("router.Push uri is empty");
233 return;
234 }
235 std::string url = target.url;
236
237 PopPage("", false, false);
238
239 RouterPageInfo info { url };
240 LoadPage(GenerateNextPageId(), info, params, false, false, false);
241 }
242
BackCheckAlert(const RouterPageInfo & target,const std::string & params)243 void CJPageRouter::BackCheckAlert(const RouterPageInfo& target, const std::string& params)
244 {
245 {
246 ProcessGuard guard(this);
247 if (pageRouterStack_.empty()) {
248 LOGI("page route stack is empty");
249 return;
250 }
251 auto topPage = GetTopPage();
252 CHECK_NULL_VOID(topPage);
253
254 if (!topPage->FireDeclarativeOnBackPressCallback()) {
255 return;
256 }
257 }
258
259 StartPop();
260 }
261
StartClean()262 void CJPageRouter::StartClean()
263 {
264 ProcessGuard guard(this);
265 if (pageRouterStack_.size() <= 1) {
266 LOGW("current page stack can not clean, %{public}d", static_cast<int32_t>(pageRouterStack_.size()));
267 return;
268 }
269 std::list<RefPtr<JsAcePage>> temp;
270 std::swap(temp, pageRouterStack_);
271 pageRouterStack_.emplace_back(temp.back());
272 if (!OnCleanPageStack()) {
273 LOGE("fail to clean page");
274 std::swap(temp, pageRouterStack_);
275 }
276 }
277
PopPage(const std::string & params,bool needShowNext,bool needTransition)278 void CJPageRouter::PopPage(const std::string& params, bool needShowNext, bool needTransition)
279 {
280 if (pageRouterStack_.empty()) {
281 LOGE("page router stack size is illegal");
282 return;
283 }
284 if (needShowNext && (pageRouterStack_.size() == 1)) {
285 LOGE("page router stack size is only one, can not show next");
286 return;
287 }
288 auto topNode = pageRouterStack_.back();
289 pageRouterStack_.pop_back();
290
291 if (params.empty()) {
292 if (!OnPopPage(needShowNext, needTransition)) {
293 LOGE("fail to pop page");
294 pageRouterStack_.emplace_back(topNode);
295 }
296 return;
297 }
298
299 if (OnPopPage(needShowNext, needTransition)) {
300 return;
301 }
302 LOGE("fail to pop page");
303 // restore stack and pageParam.
304 pageRouterStack_.emplace_back(topNode);
305 }
306
PopPageToIndex(int32_t index,const std::string & params,bool needShowNext,bool needTransition)307 void CJPageRouter::PopPageToIndex(int32_t index, const std::string& params, bool needShowNext, bool needTransition)
308 {
309 LOGD("PopPageToIndex to index: %{public}d", index);
310 std::list<RefPtr<JsAcePage>> temp;
311 std::swap(temp, pageRouterStack_);
312 auto iter = temp.begin();
313 for (int32_t current = 0; current <= index; ++current) {
314 pageRouterStack_.emplace_back(*iter);
315 iter++;
316 }
317 if (params.empty()) {
318 if (!OnPopPageToIndex(index, needShowNext, needTransition)) {
319 LOGE("fail to pop page to index");
320 std::swap(temp, pageRouterStack_);
321 }
322 return;
323 }
324
325 if (OnPopPageToIndex(index, needShowNext, needTransition)) {
326 return;
327 }
328 LOGE("fail to pop page to index");
329 // restore stack and pageParam.
330 std::swap(temp, pageRouterStack_);
331 }
332
OnPopPage(bool needShowNext,bool needTransition)333 bool CJPageRouter::OnPopPage(bool needShowNext, bool needTransition)
334 {
335 auto frontend = frontend_.Upgrade();
336 CHECK_NULL_RETURN(frontend, false);
337 auto pipeline = AceType::DynamicCast<PipelineContext>(frontend->GetPipelineContext());
338 CHECK_NULL_RETURN(pipeline, false);
339 if (GetStackSize() == 1) {
340 if (!AllowPopLastPage()) {
341 return false;
342 }
343 OnHideCurrent();
344 return true;
345 }
346 if (!pipeline->CanPopPage()) {
347 return false;
348 }
349 OnHideCurrent();
350 pipeline->RemovePageTransitionListener(lastTransitionListener_);
351 lastTransitionListener_ = pipeline->AddPageTransitionListener(
352 [weak = WeakClaim(this), pageId = GetTopPage()->GetPageId()](
353 const TransitionEvent& event, const WeakPtr<PageElement>& in, const WeakPtr<PageElement>& out) {
354 auto delegate = weak.Upgrade();
355 if (delegate) {
356 if (event == TransitionEvent::PUSH_END) {
357 delegate->OnPopSuccess();
358 }
359 }
360 });
361 return true;
362 }
363
OnPopPageToIndex(int32_t index,bool needShowNext,bool needTransition)364 bool CJPageRouter::OnPopPageToIndex(int32_t index, bool needShowNext, bool needTransition)
365 {
366 return false;
367 }
368
OnCleanPageStack()369 bool CJPageRouter::OnCleanPageStack()
370 {
371 return true;
372 }
373
OnPopSuccess()374 void CJPageRouter::OnPopSuccess()
375 {
376 SetCurrentPage();
377 OnShowCurrent();
378 }
379
SetCurrentPage()380 void CJPageRouter::SetCurrentPage()
381 {
382 auto page = GetTopPage();
383 CHECK_NULL_VOID(page);
384 auto frontend = frontend_.Upgrade();
385 CHECK_NULL_VOID(frontend);
386 auto accessManager = frontend->GetAccessibilityManager();
387 CHECK_NULL_VOID(accessManager);
388 accessManager->SetVersion(AccessibilityVersion::JS_DECLARATIVE_VERSION);
389 auto nodeManager = DynamicCast<AccessibilityNodeManager>(accessManager);
390 nodeManager->SetRunningPage(page);
391 OnPageUpdate(page, false);
392 }
393
FlushPage(const RefPtr<OHOS::Ace::Framework::JsAcePage> & page,const std::string & url)394 void CJPageRouter::FlushPage(const RefPtr<OHOS::Ace::Framework::JsAcePage>& page, const std::string& url)
395 {
396 if (page->FragmentCount() == 1) {
397 OnPageReady(page, url, pageRouterStack_.size() > 1);
398 } else {
399 OnPageUpdate(page, true);
400 }
401 }
402
PostTask(std::function<void ()> callback,bool isUI)403 void CJPageRouter::PostTask(std::function<void()> callback, bool isUI)
404 {
405 auto frontend = frontend_.Upgrade();
406 CHECK_NULL_VOID(frontend);
407 auto executor = frontend->GetTaskExecutor();
408 CHECK_NULL_VOID(executor);
409 executor->PostTask(
410 std::move(callback), isUI ? TaskExecutor::TaskType::UI : TaskExecutor::TaskType::JS, "CJPageRouterPostTask");
411 }
412
OnPageReady(const RefPtr<JsAcePage> & page,const std::string & url,bool needHideLast)413 void CJPageRouter::OnPageReady(const RefPtr<JsAcePage>& page, const std::string& url, bool needHideLast)
414 {
415 loadingPage_ = nullptr;
416 auto frontend = frontend_.Upgrade();
417 CHECK_NULL_VOID(frontend);
418 auto jsCommands = std::make_shared<std::vector<RefPtr<JsCommand>>>();
419 page->PopAllCommands(*jsCommands);
420
421 auto pipelineContext = AceType::DynamicCast<PipelineContext>(frontend->GetPipelineContext());
422 CHECK_NULL_VOID(pipelineContext);
423
424 page->SetPipelineContext(pipelineContext);
425
426 PostTask([weak = WeakClaim(this), jsCommands, url, page, pipelineContext, needHideLast] {
427 auto self = weak.Upgrade();
428 CHECK_NULL_VOID(self);
429 // Flush all JS commands.
430 for (const auto& command : *jsCommands) {
431 command->Execute(page);
432 }
433 // Just clear all dirty nodes.
434 page->ClearAllDirtyNodes();
435 if (page->GetDomDocument()) {
436 page->GetDomDocument()->HandleComponentPostBinding();
437 }
438 if (pipelineContext->GetAccessibilityManager()) {
439 pipelineContext->GetAccessibilityManager()->HandleComponentPostBinding();
440 }
441
442 if (!pipelineContext->CanPushPage()) {
443 LOGW("router push run in unexpected process");
444 return;
445 }
446 self->OnPrePageChange(page);
447 pipelineContext->RemovePageTransitionListener(self->lastTransitionListener_);
448 self->lastTransitionListener_ = pipelineContext->AddPageTransitionListener(
449 [weak, page](
450 const TransitionEvent& event, const WeakPtr<PageElement>& in, const WeakPtr<PageElement>& out) {
451 auto delegate = weak.Upgrade();
452 if (event == TransitionEvent::PUSH_END) {
453 delegate->OnPopSuccess();
454 }
455 });
456 pipelineContext->PushPage(page->BuildPage(url), page->GetStageElement());
457 });
458 }
459
OnPageUpdate(const RefPtr<OHOS::Ace::Framework::JsAcePage> & page,bool directExecute)460 void CJPageRouter::OnPageUpdate(const RefPtr<OHOS::Ace::Framework::JsAcePage>& page, bool directExecute)
461 {
462 auto frontend = frontend_.Upgrade();
463 CHECK_NULL_VOID(frontend);
464 auto pipeline = AceType::DynamicCast<PipelineContext>(frontend->GetPipelineContext());
465 CHECK_NULL_VOID(pipeline);
466
467 auto jsCommands = std::make_shared<std::vector<RefPtr<JsCommand>>>();
468 page->PopAllCommands(*jsCommands);
469 WeakPtr<JsAcePage> weakPage = page;
470 WeakPtr<PipelineContext> weakContext = pipeline;
471 pipeline->AddPageUpdateTask(
472 [weakPage = std::move(weakPage), weakContext = std::move(weakContext), jsCommands] {
473 auto jsPage = weakPage.Upgrade();
474 auto context = weakContext.Upgrade();
475 if (!jsPage || !context) {
476 LOGE("Page update failed. page or context is null.");
477 return;
478 }
479 // Flush all JS commands.
480 for (const auto& command : *jsCommands) {
481 command->Execute(jsPage);
482 }
483 if (jsPage->GetDomDocument()) {
484 jsPage->GetDomDocument()->HandleComponentPostBinding();
485 }
486 auto accessibilityManager = context->GetAccessibilityManager();
487 if (accessibilityManager) {
488 accessibilityManager->HandleComponentPostBinding();
489 }
490
491 jsPage->ClearShowCommand();
492 std::vector<NodeId> dirtyNodes;
493 jsPage->PopAllDirtyNodes(dirtyNodes);
494 for (auto nodeId : dirtyNodes) {
495 auto patchComponent = jsPage->BuildPagePatch(nodeId);
496 if (patchComponent) {
497 context->ScheduleUpdate(patchComponent);
498 }
499 }
500 },
501 directExecute);
502 }
503
OnPrePageChange(const RefPtr<OHOS::Ace::Framework::JsAcePage> & page)504 void CJPageRouter::OnPrePageChange(const RefPtr<OHOS::Ace::Framework::JsAcePage>& page)
505 {
506 CHECK_NULL_VOID(page);
507 auto document = page->GetDomDocument();
508 CHECK_NULL_VOID(document);
509 auto frontend = frontend_.Upgrade();
510 CHECK_NULL_VOID(frontend);
511 auto accessManager = frontend->GetAccessibilityManager();
512 CHECK_NULL_VOID(accessManager);
513 accessManager->SetRootNodeId(document->GetRootNodeId());
514 }
515 } // namespace OHOS::Ace::Framework
516