1 /*
2 * Copyright (c) 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 "bridge/declarative_frontend/jsview/js_node_container.h"
17
18 #include <functional>
19 #include <mutex>
20 #include <unistd.h>
21
22 #include "base/geometry/ng/size_t.h"
23 #include "base/utils/utils.h"
24 #include "bridge/common/utils/engine_helper.h"
25 #include "bridge/declarative_frontend/engine/functions/js_function.h"
26 #include "bridge/declarative_frontend/engine/js_converter.h"
27 #include "bridge/declarative_frontend/engine/jsi/jsi_types.h"
28 #include "bridge/declarative_frontend/engine/jsi/nativeModule/ui_context_helper.h"
29 #include "bridge/declarative_frontend/jsview/js_base_node.h"
30 #include "core/common/container_scope.h"
31 #include "core/components_ng/base/view_abstract_model.h"
32 #include "core/components_ng/base/view_stack_processor.h"
33 #include "core/components_ng/pattern/node_container/node_container_model_ng.h"
34 #include "core/components_ng/pattern/node_container/node_container_pattern.h"
35
36 namespace OHOS::Ace {
37 namespace {
38 const char* NODE_CONTAINER_ID = "_nodeContainerId";
39 const char* INTERNAL_FIELD_VALUE = "_value";
40 const char* NODEPTR_OF_UINODE = "nodePtr_";
41 constexpr int32_t INVALID_NODE_CONTAINER_ID = -1;
42
BindFunc(const Framework::JSCallbackInfo & info,const RefPtr<NG::FrameNode> & frameNode)43 void BindFunc(const Framework::JSCallbackInfo& info, const RefPtr<NG::FrameNode>& frameNode)
44 {
45 CHECK_NULL_VOID(frameNode);
46 CHECK_NULL_VOID(!frameNode->HasOnNodeDestroyCallback());
47 EcmaVM* vm = info.GetVm();
48 auto global = JSNApi::GetGlobalObject(vm);
49 auto funcName = panda::StringRef::NewFromUtf8(vm, "__RemoveFromNodeControllerMap__");
50 auto obj = global->Get(vm, funcName);
51 if (obj->IsFunction(vm)) {
52 panda::Local<panda::FunctionRef> detachFunc = obj;
53 frameNode->SetOnNodeDestroyCallback([vm, func = panda::CopyableGlobal(vm, detachFunc)](int32_t nodeId) {
54 panda::Local<panda::JSValueRef> params[] = { panda::NumberRef::New(vm, nodeId) };
55 func->Call(vm, func.ToLocal(), params, ArraySize(params));
56 });
57 }
58 }
59
AddToNodeControllerMap(EcmaVM * vm,int32_t nodeId,const Framework::JSRef<Framework::JSObject> & object)60 void AddToNodeControllerMap(EcmaVM* vm, int32_t nodeId, const Framework::JSRef<Framework::JSObject>& object)
61 {
62 auto global = JSNApi::GetGlobalObject(vm);
63 auto funcName = panda::StringRef::NewFromUtf8(vm, "__AddToNodeControllerMap__");
64 auto obj = global->Get(vm, funcName);
65 if (obj->IsFunction(vm)) {
66 panda::Local<panda::FunctionRef> attachFunc = obj;
67 panda::Local<panda::JSValueRef> params[] = { panda::NumberRef::New(vm, nodeId),
68 panda::CopyableGlobal(vm, object->GetLocalHandle()).ToLocal() };
69 attachFunc->Call(vm, attachFunc, params, ArraySize(params));
70 }
71 }
72 } // namespace
73
74 std::unique_ptr<NodeContainerModel> NodeContainerModel::instance_;
75 std::mutex NodeContainerModel::mutex_;
76
GetInstance()77 NodeContainerModel* NodeContainerModel::GetInstance()
78 {
79 if (!instance_) {
80 std::lock_guard<std::mutex> lock(mutex_);
81 if (!instance_) {
82 #ifdef NG_BUILD
83 instance_.reset(new NG::NodeContainerModelNG());
84 #else
85 if (Container::IsCurrentUseNewPipeline()) {
86 instance_.reset(new NG::NodeContainerModelNG());
87 } else {
88 LOGE("NodeContainerModelNG is not supported in OldPipeline.");
89 return nullptr;
90 }
91 #endif
92 }
93 }
94 return instance_.get();
95 }
96 } // namespace OHOS::Ace
97
98 namespace OHOS::Ace::Framework {
Create(const JSCallbackInfo & info)99 void JSNodeContainer::Create(const JSCallbackInfo& info)
100 {
101 NodeContainerModel* nodeContainerModelInstance = NodeContainerModel::GetInstance();
102 CHECK_NULL_VOID(nodeContainerModelInstance);
103 nodeContainerModelInstance->Create();
104 auto frameNode = NG::ViewStackProcessor::GetInstance()->GetMainFrameNode();
105 CHECK_NULL_VOID(frameNode);
106 if (info.Length() < 1 || !info[0]->IsObject() || info[0]->IsNull()) {
107 frameNode->RemoveChildAtIndex(0);
108 frameNode->MarkNeedFrameFlushDirty(NG::PROPERTY_UPDATE_MEASURE);
109 ResetNodeController();
110 return;
111 }
112 auto object = JSRef<JSObject>::Cast(info[0]);
113
114 JSObject firstArg = JSRef<JSObject>::Cast(info[0]).Get();
115 auto nodeContainerId = frameNode->GetId();
116 // check if it's the same object, and if it is, return it;
117 auto internalField = firstArg->GetProperty(NODE_CONTAINER_ID);
118 if (internalField->IsObject()) {
119 auto obj = JSRef<JSObject>::Cast(internalField);
120 auto insideId = obj->GetProperty(INTERNAL_FIELD_VALUE);
121 if (insideId->IsNumber()) {
122 auto id = insideId->ToNumber<int32_t>();
123 if (id == nodeContainerId) {
124 return;
125 }
126 }
127 }
128 // clear the _nodeContainerId in pre controller;
129 nodeContainerModelInstance->ResetController();
130
131 BindFunc(info, AceType::Claim(frameNode));
132 AddToNodeControllerMap(info.GetVm(), frameNode->GetId(), object);
133 // set a function to reset the _nodeContainerId in controller;
134 auto resetFunc = [firstArg = JSWeak<JSObject>(object)]() {
135 CHECK_NULL_VOID(!firstArg.IsEmpty());
136 JSObject args = firstArg.Lock().Get();
137 auto internalField = args->GetProperty(NODE_CONTAINER_ID);
138 CHECK_NULL_VOID(internalField->IsObject());
139 auto obj = JSRef<JSObject>::Cast(internalField);
140 obj->SetProperty(INTERNAL_FIELD_VALUE, INVALID_NODE_CONTAINER_ID);
141 };
142 nodeContainerModelInstance->BindController(std::move(resetFunc));
143 auto execCtx = info.GetExecutionContext();
144
145 SetNodeController(object, execCtx);
146 // set the _nodeContainerId to nodeController
147 if (internalField->IsObject()) {
148 auto obj = JSRef<JSObject>::Cast(internalField);
149 obj->SetProperty(INTERNAL_FIELD_VALUE, nodeContainerId);
150 }
151 nodeContainerModelInstance->FireMakeNode();
152 }
153
SetNodeController(const JSRef<JSObject> & object,JsiExecutionContext execCtx)154 void JSNodeContainer::SetNodeController(const JSRef<JSObject>& object, JsiExecutionContext execCtx)
155 {
156 // get the function to makeNode
157 JSRef<JSVal> jsMakeNodeFunc = object->GetProperty("__makeNode__");
158 if (!jsMakeNodeFunc->IsFunction()) {
159 ResetNodeController();
160 return;
161 }
162 NodeContainerModel* nodeContainerModelInstance = NodeContainerModel::GetInstance();
163 CHECK_NULL_VOID(nodeContainerModelInstance);
164
165 auto jsFunc = JSRef<JSFunc>::Cast(jsMakeNodeFunc);
166 auto containerId = Container::CurrentId();
167 RefPtr<JsFunction> jsMake = AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(object), jsFunc);
168 nodeContainerModelInstance->SetMakeFunction(
169 [func = std::move(jsMake), containerId, execCtx]() -> RefPtr<NG::UINode> {
170 JAVASCRIPT_EXECUTION_SCOPE(execCtx);
171 ContainerScope scope(containerId);
172 panda::Local<panda::JSValueRef> uiContext = NG::UIContextHelper::GetUIContext(execCtx.vm_, containerId);
173 auto jsVal = JSRef<JSVal>::Make(uiContext);
174 JSRef<JSVal> result = func->ExecuteJS(1, &jsVal);
175 if (result.IsEmpty() || !result->IsObject()) {
176 return nullptr;
177 }
178 JSRef<JSObject> obj = JSRef<JSObject>::Cast(result);
179 JSRef<JSVal> nodeptr = obj->GetProperty(NODEPTR_OF_UINODE);
180 if (nodeptr.IsEmpty()) {
181 return nullptr;
182 }
183 const auto* vm = nodeptr->GetEcmaVM();
184 auto* node = nodeptr->GetLocalHandle()->ToNativePointer(vm)->Value();
185 auto* uiNode = reinterpret_cast<NG::UINode*>(node);
186 CHECK_NULL_RETURN(uiNode, nullptr);
187 return AceType::Claim(uiNode);
188 });
189
190 SetOnAppearFunc(object, execCtx);
191 SetOnDisappearFunc(object, execCtx);
192 SetOnAttachFunc(object, execCtx);
193 SetOnDetachFunc(object, execCtx);
194 SetOnResizeFunc(object, execCtx);
195 SetOnTouchEventFunc(object, execCtx);
196 }
197
ResetNodeController()198 void JSNodeContainer::ResetNodeController()
199 {
200 NodeContainerModel* nodeContainerModelInstance = NodeContainerModel::GetInstance();
201 CHECK_NULL_VOID(nodeContainerModelInstance);
202 nodeContainerModelInstance->ResetController();
203 nodeContainerModelInstance->SetMakeFunction(nullptr);
204 nodeContainerModelInstance->SetOnTouchEvent(nullptr);
205 nodeContainerModelInstance->SetOnResize(nullptr);
206 nodeContainerModelInstance->SetOnAppear(nullptr);
207 nodeContainerModelInstance->SetOnDisAppear(nullptr);
208 ViewAbstractModel::GetInstance()->SetOnAttach(nullptr);
209 ViewAbstractModel::GetInstance()->SetOnDetach(nullptr);
210 }
211
SetOnAppearFunc(const JSRef<JSObject> & object,JsiExecutionContext execCtx)212 void JSNodeContainer::SetOnAppearFunc(const JSRef<JSObject>& object, JsiExecutionContext execCtx)
213 {
214 auto showCallback = object->GetProperty("aboutToAppear");
215 CHECK_NULL_VOID(showCallback->IsFunction());
216 NodeContainerModel* nodeContainerModelInstance = NodeContainerModel::GetInstance();
217 CHECK_NULL_VOID(nodeContainerModelInstance);
218 RefPtr<JsFunction> jsAppearFunc =
219 AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(object), JSRef<JSFunc>::Cast(showCallback));
220 auto onAppear = [func = std::move(jsAppearFunc), execCtx]() {
221 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
222 func->Execute();
223 };
224 nodeContainerModelInstance->SetOnAppear(onAppear);
225 }
226
SetOnDisappearFunc(const JSRef<JSObject> & object,JsiExecutionContext execCtx)227 void JSNodeContainer::SetOnDisappearFunc(const JSRef<JSObject>& object, JsiExecutionContext execCtx)
228 {
229 auto dismissCallback = object->GetProperty("aboutToDisappear");
230 CHECK_NULL_VOID(dismissCallback->IsFunction());
231 NodeContainerModel* nodeContainerModelInstance = NodeContainerModel::GetInstance();
232 CHECK_NULL_VOID(nodeContainerModelInstance);
233 RefPtr<JsFunction> jsDisappearFunc =
234 AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(object), JSRef<JSFunc>::Cast(dismissCallback));
235 auto onDisappear = [func = std::move(jsDisappearFunc), execCtx]() {
236 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
237 func->Execute();
238 };
239 nodeContainerModelInstance->SetOnDisAppear(onDisappear);
240 }
241
SetOnAttachFunc(const JSRef<JSObject> & object,JsiExecutionContext execCtx)242 void JSNodeContainer::SetOnAttachFunc(const JSRef<JSObject>& object, JsiExecutionContext execCtx)
243 {
244 auto showCallback = object->GetProperty("aboutToAttach");
245 CHECK_NULL_VOID(showCallback->IsFunction());
246 RefPtr<JsFunction> jsAppearFunc =
247 AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(object), JSRef<JSFunc>::Cast(showCallback));
248 auto onAttach = [func = std::move(jsAppearFunc), execCtx]() {
249 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
250 func->Execute();
251 };
252 ViewAbstractModel::GetInstance()->SetOnAttach(onAttach);
253 }
254
SetOnDetachFunc(const JSRef<JSObject> & object,JsiExecutionContext execCtx)255 void JSNodeContainer::SetOnDetachFunc(const JSRef<JSObject>& object, JsiExecutionContext execCtx)
256 {
257 auto dismissCallback = object->GetProperty("aboutToDetach");
258 CHECK_NULL_VOID(dismissCallback->IsFunction());
259 RefPtr<JsFunction> jsDisappearFunc =
260 AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(object), JSRef<JSFunc>::Cast(dismissCallback));
261 auto onDetach = [func = std::move(jsDisappearFunc), execCtx]() {
262 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
263 func->Execute();
264 };
265 ViewAbstractModel::GetInstance()->SetOnDetach(onDetach);
266 }
267
SetOnTouchEventFunc(const JSRef<JSObject> & object,JsiExecutionContext execCtx)268 void JSNodeContainer::SetOnTouchEventFunc(const JSRef<JSObject>& object, JsiExecutionContext execCtx)
269 {
270 auto onTouchEventCallback = object->GetProperty("onTouchEvent");
271 CHECK_NULL_VOID(onTouchEventCallback->IsFunction());
272 NodeContainerModel* nodeContainerModelInstance = NodeContainerModel::GetInstance();
273 CHECK_NULL_VOID(nodeContainerModelInstance);
274 RefPtr<JsTouchFunction> jsOnTouchFunc =
275 AceType::MakeRefPtr<JsTouchFunction>(JSRef<JSObject>(object), JSRef<JSFunc>::Cast(onTouchEventCallback));
276 WeakPtr<NG::FrameNode> frameNode = AceType::WeakClaim(NG::ViewStackProcessor::GetInstance()->GetMainFrameNode());
277 auto onTouch = [execCtx, func = std::move(jsOnTouchFunc), node = frameNode](TouchEventInfo& info) {
278 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
279 PipelineContext::SetCallBackNode(node);
280 func->Execute(info);
281 };
282 nodeContainerModelInstance->SetOnTouchEvent(std::move(onTouch));
283 }
284
SetOnResizeFunc(const JSRef<JSObject> & object,JsiExecutionContext execCtx)285 void JSNodeContainer::SetOnResizeFunc(const JSRef<JSObject>& object, JsiExecutionContext execCtx)
286 {
287 auto aboutToResize = object->GetProperty("aboutToResize");
288 CHECK_NULL_VOID(aboutToResize->IsFunction());
289 NodeContainerModel* nodeContainerModelInstance = NodeContainerModel::GetInstance();
290 CHECK_NULL_VOID(nodeContainerModelInstance);
291 RefPtr<JsFunction> jsAboutToResizeFunc =
292 AceType::MakeRefPtr<JsFunction>(JSRef<JSObject>(object), JSRef<JSFunc>::Cast(aboutToResize));
293 auto onResize = [func = std::move(jsAboutToResizeFunc), execCtx](const NG::SizeF& size) {
294 JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
295 JSRef<JSObjTemplate> objectTemplate = JSRef<JSObjTemplate>::New();
296 objectTemplate->SetInternalFieldCount(1);
297 JSRef<JSObject> obj = objectTemplate->NewInstance();
298 obj->SetProperty<double>("width", PipelineBase::Px2VpWithCurrentDensity(size.Width()));
299 obj->SetProperty<double>("height", PipelineBase::Px2VpWithCurrentDensity(size.Height()));
300 JSRef<JSVal> param = JSRef<JSVal>::Cast(obj);
301 func->ExecuteJS(1, ¶m);
302 };
303 nodeContainerModelInstance->SetOnResize(onResize);
304 }
305
GetCurrentContext()306 JSRef<JSVal> JSNodeContainer::GetCurrentContext()
307 {
308 auto container = Container::Current();
309 CHECK_NULL_RETURN(container, JSRef<JSVal>());
310 auto frontend = container->GetFrontend();
311 CHECK_NULL_RETURN(frontend, JSRef<JSVal>());
312 auto context = frontend->GetContextValue();
313 auto jsVal = JsConverter::ConvertNapiValueToJsVal(context);
314 return jsVal;
315 }
316
JSBind(BindingTarget globalObj)317 void JSNodeContainer::JSBind(BindingTarget globalObj)
318 {
319 JSClass<JSNodeContainer>::Declare("NodeContainer");
320
321 MethodOptions opt = MethodOptions::NONE;
322 JSClass<JSNodeContainer>::StaticMethod("create", &JSNodeContainer::Create, opt);
323 JSClass<JSNodeContainer>::StaticMethod("onAttach", &JSInteractableView::JsOnAttach);
324 JSClass<JSNodeContainer>::StaticMethod("onAppear", &JSInteractableView::JsOnAppear);
325 JSClass<JSNodeContainer>::StaticMethod("onDetach", &JSInteractableView::JsOnDetach);
326 JSClass<JSNodeContainer>::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear);
327 JSClass<JSNodeContainer>::StaticMethod("onTouch", &JSInteractableView::JsOnTouch);
328 JSClass<JSNodeContainer>::StaticMethod("onHover", &JSInteractableView::JsOnHover);
329 JSClass<JSNodeContainer>::StaticMethod("onKeyEvent", &JSInteractableView::JsOnKey);
330 JSClass<JSNodeContainer>::StaticMethod("onDeleteEvent", &JSInteractableView::JsOnDelete);
331
332 JSClass<JSNodeContainer>::InheritAndBind<JSViewAbstract>(globalObj);
333 }
334 } // namespace OHOS::Ace::Framework
335