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/declarative_frontend/jsview/js_nav_path_stack.h"
17 
18 #include "base/log/ace_scoring_log.h"
19 #include "base/memory/ace_type.h"
20 #include "base/utils/utils.h"
21 #include "bridge/common/utils/engine_helper.h"
22 #include "bridge/declarative_frontend/engine/functions/js_function.h"
23 #include "bridge/declarative_frontend/engine/js_converter.h"
24 #include "bridge/declarative_frontend/engine/js_ref_ptr.h"
25 #include "bridge/declarative_frontend/engine/js_types.h"
26 #include "bridge/declarative_frontend/jsview/js_utils.h"
27 #include "core/components_ng/pattern/navigation/navigation_pattern.h"
28 #include "core/components_ng/pattern/navigation/navigation_group_node.h"
29 #include "core/pipeline_ng/pipeline_context.h"
30 #include "interfaces/napi/kits/utils/napi_utils.h"
31 
32 namespace OHOS::Ace::Framework {
33 namespace {
34 constexpr char JS_NAV_PATH_STACK_CLASS_NAME[] = "NavPathStack";
35 constexpr char JS_SET_NATIVESTACK_FUNC[] = "setNativeStack";
36 constexpr int32_t ARGC_ONE = 1;
37 using ErrorCallback = std::function<void(const std::string&, int32_t)>;
38 
39 struct NavgationAsyncContext {
40     napi_env env = nullptr;
41     napi_deferred deferred = nullptr;
42     std::string pathName;
43     JSRef<JSVal> param;
44     JSRef<JSObject> navPathInfo;
45 };
46 
ErrorToMessage(int32_t code)47 std::string ErrorToMessage(int32_t code)
48 {
49     switch (code) {
50         case ERROR_CODE_INTERNAL_ERROR:
51             return "Internal error.";
52         case ERROR_CODE_DESTINATION_NOT_FOUND:
53             return "NavDestination not found.";
54         case ERROR_CODE_BUILDER_FUNCTION_NOT_REGISTERED:
55             return "Builder function not registered.";
56         case ERROR_CODE_PARAM_INVALID:
57             return "Paramter error.";
58         default:
59             return "Error code is not supported.";
60     }
61 }
62 
ProcessPromiseCallback(std::shared_ptr<NavgationAsyncContext> asyncContext,int32_t callbackCode)63 void ProcessPromiseCallback(std::shared_ptr<NavgationAsyncContext> asyncContext, int32_t callbackCode)
64 {
65     CHECK_NULL_VOID(asyncContext);
66     CHECK_NULL_VOID(asyncContext->env);
67     CHECK_NULL_VOID(asyncContext->deferred);
68     napi_handle_scope scope = nullptr;
69     auto status = napi_open_handle_scope(asyncContext->env, &scope);
70     if (status != napi_ok) {
71         return;
72     }
73     CHECK_NULL_VOID(scope);
74     if (callbackCode == ERROR_CODE_NO_ERROR) {
75         napi_value result = nullptr;
76         napi_get_undefined(asyncContext->env, &result);
77         napi_resolve_deferred(asyncContext->env, asyncContext->deferred, result);
78     } else {
79         napi_value code = nullptr;
80         napi_create_int32(asyncContext->env, callbackCode, &code);
81 
82         napi_value msg = nullptr;
83         std::string strMsg = ErrorToMessage(callbackCode);
84         napi_create_string_utf8(asyncContext->env, strMsg.c_str(), strMsg.length(), &msg);
85 
86         napi_value error = nullptr;
87         napi_create_error(asyncContext->env, code, msg, &error);
88         napi_reject_deferred(asyncContext->env, asyncContext->deferred, error);
89     }
90     napi_close_handle_scope(asyncContext->env, scope);
91 }
92 
ReturnPromise(const JSCallbackInfo & info,napi_value result)93 void ReturnPromise(const JSCallbackInfo& info, napi_value result)
94 {
95     CHECK_NULL_VOID(result);
96     auto jsPromise = JsConverter::ConvertNapiValueToJsVal(result);
97     if (!jsPromise->IsObject()) {
98         return;
99     }
100     info.SetReturnValue(JSRef<JSObject>::Cast(jsPromise));
101 }
102 }
103 
JSBind(BindingTarget globalObj)104 void JSNavPathStack::JSBind(BindingTarget globalObj)
105 {
106     JSClass<JSNavPathStack>::Declare("NativeNavPathStack");
107     JSClass<JSNavPathStack>::Method("onStateChanged", &JSNavPathStack::OnStateChanged);
108     JSClass<JSNavPathStack>::CustomMethod("onPushDestination", &JSNavPathStack::OnPushDestination);
109     JSClass<JSNavPathStack>::Bind(globalObj, &JSNavPathStack::Constructor, &JSNavPathStack::Destructor);
110 }
111 
Constructor(const JSCallbackInfo & info)112 void JSNavPathStack::Constructor(const JSCallbackInfo& info)
113 {
114     auto stack = Referenced::MakeRefPtr<JSNavPathStack>();
115     stack->IncRefCount();
116     info.SetReturnValue(Referenced::RawPtr(stack));
117 }
118 
Destructor(JSNavPathStack * stack)119 void JSNavPathStack::Destructor(JSNavPathStack* stack)
120 {
121     if (stack != nullptr) {
122         stack->DecRefCount();
123     }
124 }
125 
CreateNewNavPathStackJSObject()126 JSRef<JSObject> JSNavPathStack::CreateNewNavPathStackJSObject()
127 {
128     JSRef<JSObject> empty;
129     auto engine = EngineHelper::GetCurrentEngine();
130     CHECK_NULL_RETURN(engine, empty);
131     NativeEngine* nativeEngine = engine->GetNativeEngine();
132     CHECK_NULL_RETURN(nativeEngine, empty);
133     auto env = reinterpret_cast<napi_env>(nativeEngine);
134 
135     napi_value global;
136     napi_status ret = napi_get_global(env, &global);
137     if (ret != napi_ok) {
138         return empty;
139     }
140     napi_value constructor;
141     ret = napi_get_named_property(env, global, JS_NAV_PATH_STACK_CLASS_NAME, &constructor);
142     if (ret != napi_ok) {
143         return empty;
144     }
145 
146     napi_value obj;
147     ret = napi_new_instance(env, constructor, 0, nullptr, &obj);
148     if (ret != napi_ok) {
149         return empty;
150     }
151 
152     JSRef<JSVal> value = JsConverter::ConvertNapiValueToJsVal(obj);
153     if (!value->IsObject()) {
154         return empty;
155     }
156 
157     return JSRef<JSObject>::Cast(value);
158 }
159 
SetNativeNavPathStack(JSRef<JSObject> jsStack,JSRef<JSObject> nativeStack)160 void JSNavPathStack::SetNativeNavPathStack(JSRef<JSObject> jsStack, JSRef<JSObject> nativeStack)
161 {
162     if (jsStack->IsEmpty() || nativeStack->IsEmpty()) {
163         return;
164     }
165 
166     auto property = jsStack->GetProperty(JS_SET_NATIVESTACK_FUNC);
167     if (!property->IsFunction()) {
168         return;
169     }
170 
171     auto setNativeStackFunc = JSRef<JSFunc>::Cast(property);
172     JSRef<JSVal> params[1];
173     params[0] = JSRef<JSVal>::Cast(nativeStack);
174     setNativeStackFunc->Call(jsStack, 1, params);
175 }
176 
OnPushDestination(const JSCallbackInfo & info)177 void JSNavPathStack::OnPushDestination(const JSCallbackInfo& info)
178 {
179     ContainerScope scope(containerCurrentId_);
180     auto engine = EngineHelper::GetCurrentEngine();
181     CHECK_NULL_VOID(engine);
182     NativeEngine* nativeEngine = engine->GetNativeEngine();
183     CHECK_NULL_VOID(nativeEngine);
184 
185     auto asyncContext = std::make_shared<NavgationAsyncContext>();
186     asyncContext->env = reinterpret_cast<napi_env>(nativeEngine);
187     napi_value result = nullptr;
188     napi_create_promise(asyncContext->env, &asyncContext->deferred, &result);
189     if (info.Length() != ARGC_ONE || !info[0]->IsObject()) {
190         ProcessPromiseCallback(asyncContext, ERROR_CODE_INTERNAL_ERROR);
191         ReturnPromise(info, result);
192         return;
193     }
194     asyncContext->navPathInfo = JSRef<JSObject>::Cast(info[0]);
195     if (!(asyncContext->navPathInfo->GetProperty("name")->IsString())) {
196         ProcessPromiseCallback(asyncContext, ERROR_CODE_PARAM_INVALID);
197         ReturnPromise(info, result);
198         return;
199     }
200 
201     auto context = PipelineContext::GetCurrentContext();
202     if (context == nullptr) {
203         ProcessPromiseCallback(asyncContext, ERROR_CODE_INTERNAL_ERROR);
204         ReturnPromise(info, result);
205         return;
206     }
207 
208     auto asyncTask = [asyncContext, weakStack = WeakClaim(this)]() {
209         CHECK_NULL_VOID(asyncContext);
210         auto stack = weakStack.Upgrade();
211         if (stack == nullptr || stack->checkNavDestinationExistsFunc_ == nullptr) {
212             ProcessPromiseCallback(asyncContext, ERROR_CODE_INTERNAL_ERROR);
213             return;
214         }
215         auto errorCode = stack->checkNavDestinationExistsFunc_(asyncContext->navPathInfo);
216         ProcessPromiseCallback(asyncContext, errorCode);
217     };
218 
219     context->PostAsyncEvent(asyncTask, "ArkUINavigationPushDestination", TaskExecutor::TaskType::JS);
220     ReturnPromise(info, result);
221 }
222 
CheckIsValid(JSValueWrapper object)223 bool JSNavPathStack::CheckIsValid(JSValueWrapper object)
224 {
225     auto engine = EngineHelper::GetCurrentEngine();
226     CHECK_NULL_RETURN(engine, false);
227     NativeEngine* nativeEngine = engine->GetNativeEngine();
228     CHECK_NULL_RETURN(nativeEngine, false);
229     auto env = reinterpret_cast<napi_env>(nativeEngine);
230 
231     napi_value global;
232     napi_status ret = napi_get_global(env, &global);
233     if (ret != napi_ok) {
234         return false;
235     }
236     napi_value constructor;
237     ret = napi_get_named_property(env, global, JS_NAV_PATH_STACK_CLASS_NAME, &constructor);
238     if (ret != napi_ok) {
239         return false;
240     }
241     bool isInstance = false;
242     ScopeRAII scope(reinterpret_cast<napi_env>(nativeEngine));
243     napi_value stack = nativeEngine->ValueToNapiValue(object);
244     napi_instanceof(env, stack, constructor, &isInstance);
245     return isInstance;
246 }
247 } // namespace OHOS::Ace::Framework
248