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