1 /*
2 * Copyright (c) 2023-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 #include "bridge/declarative_frontend/jsview/js_base_node.h"
16
17 #include <memory>
18 #include <queue>
19 #include <string>
20
21 #include "canvas_napi/js_canvas.h"
22 #include "jsnapi_expo.h"
23
24 #include "base/geometry/dimension.h"
25 #include "base/memory/ace_type.h"
26 #include "base/memory/referenced.h"
27 #include "base/utils/utils.h"
28 #include "bridge/common/utils/engine_helper.h"
29 #include "bridge/declarative_frontend/engine/jsi/nativeModule/ui_context_helper.h"
30 #include "bridge/declarative_frontend/engine/functions/js_function.h"
31 #include "bridge/declarative_frontend/engine/js_converter.h"
32 #include "bridge/declarative_frontend/engine/js_ref_ptr.h"
33 #include "bridge/declarative_frontend/engine/js_types.h"
34 #include "bridge/declarative_frontend/jsview/js_utils.h"
35 #include "bridge/declarative_frontend/jsview/js_view_abstract.h"
36 #include "bridge/js_frontend/engine/jsi/js_value.h"
37 #include "core/components_ng/base/frame_node.h"
38 #include "core/components_ng/base/modifier.h"
39 #include "core/components_ng/base/ui_node.h"
40 #include "core/components_ng/base/view_stack_processor.h"
41 #include "core/components_ng/pattern/custom_frame_node/custom_frame_node.h"
42 #include "core/components_ng/pattern/render_node/render_node_pattern.h"
43 #include "core/components_ng/pattern/stack/stack_pattern.h"
44 #include "core/components_ng/render/drawing_forward.h"
45 #include "core/event/touch_event.h"
46 #include "core/pipeline/pipeline_base.h"
47 #include "core/pipeline_ng/pipeline_context.h"
48
49 namespace OHOS::Ace::Framework {
50 namespace {
51 const std::unordered_set<std::string> EXPORT_TEXTURE_SUPPORT_TYPES = { V2::JS_VIEW_ETS_TAG, V2::COMMON_VIEW_ETS_TAG };
52 constexpr int32_t INFO_LENGTH_LIMIT = 2;
53 constexpr int32_t BUILD_PARAM_INDEX_TWO = 2;
54 constexpr int32_t BUILD_PARAM_INDEX_THREE = 3;
55 constexpr int32_t BUILD_PARAM_INDEX_FOUR = 4;
56 } // namespace
57
BuildNode(const JSCallbackInfo & info)58 void JSBaseNode::BuildNode(const JSCallbackInfo& info)
59 {
60 auto builder = info[0];
61 CHECK_NULL_VOID(builder->IsFunction());
62 auto buildFunc = AceType::MakeRefPtr<JsFunction>(info.This(), JSRef<JSFunc>::Cast(builder));
63
64 auto infoLen = info.Length();
65 JSRef<JSVal> param;
66 if (infoLen >= INFO_LENGTH_LIMIT && (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)
67 || (Container::LessThanAPITargetVersion(PlatformVersion::VERSION_TWELVE) && info[1]->IsObject()))) {
68 param = info[1];
69 }
70
71 auto lazyBuilderFunc = [buildFunc, param, renderType = renderType_]() mutable {
72 NG::ViewStackProcessor::GetInstance()->SetIsBuilderNode(true);
73 NG::ViewStackProcessor::GetInstance()->SetIsExportTexture(renderType == NodeRenderType::RENDER_TYPE_TEXTURE);
74 if (!param->IsEmpty()) {
75 buildFunc->ExecuteJS(1, ¶m);
76 } else {
77 buildFunc->ExecuteJS();
78 }
79 };
80
81 NG::ScopedViewStackProcessor builderViewStackProcessor;
82 lazyBuilderFunc();
83 auto parent = viewNode_ ? viewNode_->GetParent() : nullptr;
84 auto newNode = NG::ViewStackProcessor::GetInstance()->Finish();
85 realNode_ = newNode;
86 if (newNode) {
87 newNode->SetBuilderFunc(std::move(lazyBuilderFunc));
88 }
89
90 if (newNode && (infoLen >= BUILD_PARAM_INDEX_TWO + 1)) {
91 auto updateTsNodeBuilder = info[BUILD_PARAM_INDEX_TWO];
92 EcmaVM* vm = info.GetVm();
93 auto updateTsFunc = AceType::MakeRefPtr<JsFunction>(info.This(), JSRef<JSFunc>::Cast(updateTsNodeBuilder));
94 auto updateNodeFunc = [updateTsFunc, vm](int32_t instanceId, RefPtr<NG::UINode>& node) mutable {
95 JSRef<JSVal> param[2];
96 param[0] = JSRef<JSVal>::Make(ToJSValue(instanceId));
97 param[1] = JSRef<JSVal>::Make(panda::NativePointerRef::New(vm, AceType::RawPtr(node)));
98 updateTsFunc->ExecuteJS(2, param);
99 };
100 newNode->SetUpdateNodeFunc(std::move(updateNodeFunc));
101 }
102
103 if (newNode && (infoLen >= BUILD_PARAM_INDEX_THREE + 1)) {
104 auto updateTsNodeConfig = info[BUILD_PARAM_INDEX_THREE];
105 EcmaVM* vm = info.GetVm();
106 auto updateTsConfig = AceType::MakeRefPtr<JsFunction>(info.This(), JSRef<JSFunc>::Cast(updateTsNodeConfig));
107 auto updateNodeConfig = [updateTsConfig, vm]() mutable {
108 updateTsConfig->ExecuteJS();
109 };
110 newNode->SetUpdateNodeConfig(std::move(updateNodeConfig));
111 }
112
113 bool isSupportLazyBuild = false;
114 if (infoLen >= BUILD_PARAM_INDEX_FOUR + 1) {
115 auto jsLazyBuildSupported = info[BUILD_PARAM_INDEX_FOUR];
116 if (jsLazyBuildSupported->IsBoolean()) {
117 isSupportLazyBuild = jsLazyBuildSupported->ToBoolean();
118 }
119 }
120
121 // If the node is a UINode, amount it to a BuilderProxyNode if needProxy.
122 auto flag = AceType::InstanceOf<NG::FrameNode>(newNode);
123 auto isSupportExportTexture = newNode ? EXPORT_TEXTURE_SUPPORT_TYPES.count(newNode->GetTag()) > 0 : false;
124 if (!flag && newNode) {
125 auto nodeId = ElementRegister::GetInstance()->MakeUniqueId();
126 auto proxyNode = NG::FrameNode::GetOrCreateFrameNode(
127 "BuilderProxyNode", nodeId, []() { return AceType::MakeRefPtr<NG::StackPattern>(); });
128 auto stackLayoutAlgorithm = proxyNode->GetLayoutProperty<NG::LayoutProperty>();
129 stackLayoutAlgorithm->UpdateAlignment(Alignment::TOP_LEFT);
130 proxyNode->AddChild(newNode);
131 newNode = proxyNode;
132 }
133 if (parent) {
134 if (newNode) {
135 parent->ReplaceChild(viewNode_, newNode);
136 newNode->MarkNeedFrameFlushDirty(NG::PROPERTY_UPDATE_MEASURE);
137 } else {
138 parent->RemoveChild(viewNode_);
139 parent->MarkNeedFrameFlushDirty(NG::PROPERTY_UPDATE_MEASURE);
140 }
141 }
142 viewNode_ = newNode ? AceType::DynamicCast<NG::FrameNode>(newNode) : nullptr;
143 CHECK_NULL_VOID(viewNode_);
144 ProccessNode(isSupportExportTexture, isSupportLazyBuild);
145 UpdateEnd(info);
146 CHECK_NULL_VOID(viewNode_);
147 JSRef<JSObject> thisObj = info.This();
148 JSWeak<JSObject> jsObject(thisObj);
149 viewNode_->RegisterUpdateJSInstanceCallback([jsObject, vm = info.GetVm()](int32_t id) {
150 JSRef<JSObject> jsThis = jsObject.Lock();
151 JSRef<JSVal> jsUpdateFunc = jsThis->GetProperty("updateInstance");
152 if (jsUpdateFunc->IsFunction()) {
153 auto jsFunc = JSRef<JSFunc>::Cast(jsUpdateFunc);
154 auto uiContext = NG::UIContextHelper::GetUIContext(vm, id);
155 auto jsVal = JSRef<JSVal>::Make(uiContext);
156 jsFunc->Call(jsThis, 1, &jsVal);
157 }
158 });
159 }
160
ProccessNode(bool isSupportExportTexture,bool isSupportLazyBuild)161 void JSBaseNode::ProccessNode(bool isSupportExportTexture, bool isSupportLazyBuild)
162 {
163 CHECK_NULL_VOID(viewNode_);
164 viewNode_->SetIsRootBuilderNode(true);
165 if (isSupportExportTexture) {
166 viewNode_->CreateExportTextureInfoIfNeeded();
167 auto exportTextureInfo = viewNode_->GetExportTextureInfo();
168 CHECK_NULL_VOID(exportTextureInfo);
169 exportTextureInfo->SetSurfaceId(surfaceId_);
170 exportTextureInfo->SetCurrentRenderType(renderType_);
171 }
172 if (!isSupportLazyBuild) {
173 viewNode_->Build(nullptr);
174 }
175 }
176
Create(const JSCallbackInfo & info)177 void JSBaseNode::Create(const JSCallbackInfo& info)
178 {
179 if (info.Length() >= 1 && !info[0]->IsFunction()) {
180 return;
181 }
182 if (Container::LessThanAPITargetVersion(PlatformVersion::VERSION_TWELVE) && info.Length() >= INFO_LENGTH_LIMIT
183 && !(info[1]->IsObject() || info[1]->IsUndefined() || info[1]->IsNull())) {
184 return;
185 }
186 BuildNode(info);
187 EcmaVM* vm = info.GetVm();
188 info.SetReturnValue(JSRef<JSVal>::Make(panda::NativePointerRef::New(vm, AceType::RawPtr(viewNode_))));
189 }
190
ConstructorCallback(const JSCallbackInfo & info)191 void JSBaseNode::ConstructorCallback(const JSCallbackInfo& info)
192 {
193 std::string surfaceId;
194 NodeRenderType renderType = NodeRenderType::RENDER_TYPE_DISPLAY;
195 NG::OptionalSizeF selfIdealSize;
196 if (info.Length() > 0 && info[0]->IsObject()) {
197 auto renderOption = JSRef<JSObject>::Cast(info[0]);
198 auto size = renderOption->GetProperty("selfIdealSize");
199 if (size->IsObject()) {
200 auto sizeObj = JSRef<JSObject>::Cast(size);
201 auto width = sizeObj->GetProperty("width");
202 auto widthValue = width->IsNumber() ? width->ToNumber<float>() : 0.0f;
203 widthValue = LessNotEqual(widthValue, 0.0f) ? 0.0f : widthValue;
204 auto height = sizeObj->GetProperty("height");
205 auto heightValue = height->IsNumber() ? height->ToNumber<float>() : 0.0f;
206 heightValue = LessNotEqual(heightValue, 0.0f) ? 0.0f : heightValue;
207 selfIdealSize.SetWidth(PipelineBase::Vp2PxWithCurrentDensity(widthValue));
208 selfIdealSize.SetHeight(PipelineBase::Vp2PxWithCurrentDensity(heightValue));
209 }
210 auto type = renderOption->GetProperty("type");
211 if (type->IsNumber()) {
212 renderType = static_cast<NodeRenderType>(type->ToNumber<uint32_t>());
213 }
214 auto id = renderOption->GetProperty("surfaceId");
215 if (id->IsString()) {
216 surfaceId = id->ToString();
217 }
218 }
219 auto instance = AceType::MakeRefPtr<JSBaseNode>(selfIdealSize, renderType, surfaceId);
220 instance->IncRefCount();
221 info.SetReturnValue(AceType::RawPtr(instance));
222 }
223
DestructorCallback(JSBaseNode * node)224 void JSBaseNode::DestructorCallback(JSBaseNode* node)
225 {
226 if (node != nullptr) {
227 node->DecRefCount();
228 }
229 }
230
FinishUpdateFunc(const JSCallbackInfo & info)231 void JSBaseNode::FinishUpdateFunc(const JSCallbackInfo& info)
232 {
233 NG::ViewStackProcessor::GetInstance()->FlushRerenderTask();
234 }
235
PostTouchEvent(const JSCallbackInfo & info)236 void JSBaseNode::PostTouchEvent(const JSCallbackInfo& info)
237 {
238 if (!viewNode_ || info.Length() < 1 || !info[0]->IsObject()) {
239 TAG_LOGW(AceLogTag::ACE_INPUTKEYFLOW, "PostTouchEvent params invalid");
240 info.SetReturnValue(JSRef<JSVal>::Make(ToJSValue(false)));
241 return;
242 }
243 TouchEvent touchEvent = InitTouchEvent(info);
244 auto pipelineContext = NG::PipelineContext::GetCurrentContext();
245 if (!pipelineContext) {
246 TAG_LOGW(AceLogTag::ACE_INPUTKEYFLOW, "PostTouchEvent pipelineContext is invalid");
247 info.SetReturnValue(JSRef<JSVal>::Make(ToJSValue(false)));
248 return;
249 }
250 auto postEventManager = pipelineContext->GetPostEventManager();
251 if (!postEventManager) {
252 TAG_LOGW(AceLogTag::ACE_INPUTKEYFLOW, "PostTouchEvent postEventManager is invalid");
253 info.SetReturnValue(JSRef<JSVal>::Make(ToJSValue(false)));
254 return;
255 }
256 auto result = postEventManager->PostEvent(viewNode_, touchEvent);
257 info.SetReturnValue(JSRef<JSVal>::Make(ToJSValue(result)));
258 }
259
InitTouchEvent(const JSCallbackInfo & info)260 TouchEvent JSBaseNode::InitTouchEvent(const JSCallbackInfo& info)
261 {
262 TouchEvent touchEvent;
263 auto obj = JSRef<JSObject>::Cast(info[0]);
264 auto typeJsVal = obj->GetProperty("type");
265 if (typeJsVal->IsNumber()) {
266 touchEvent.type = static_cast<TouchType>(typeJsVal->ToNumber<int32_t>());
267 }
268 auto sourceJsVal = obj->GetProperty("source");
269 if (sourceJsVal->IsNumber()) {
270 touchEvent.sourceType = static_cast<SourceType>((sourceJsVal->ToNumber<int32_t>()));
271 }
272 auto sourceToolJsVal = obj->GetProperty("sourceTool");
273 if (sourceToolJsVal->IsNumber()) {
274 touchEvent.sourceTool = static_cast<SourceTool>((sourceToolJsVal->ToNumber<int32_t>()));
275 }
276 auto pressureJsVal = obj->GetProperty("pressure");
277 if (pressureJsVal->IsNumber()) {
278 touchEvent.force = sourceToolJsVal->ToNumber<float>();
279 }
280 auto timestampJsVal = obj->GetProperty("timestamp");
281 if (timestampJsVal->IsNumber()) {
282 std::chrono::nanoseconds nanoseconds(static_cast<int64_t>(timestampJsVal->ToNumber<double>()));
283 TimeStamp time(nanoseconds);
284 touchEvent.time = time;
285 }
286 auto deviceIdJsVal = obj->GetProperty("deviceId");
287 if (deviceIdJsVal->IsNumber()) {
288 touchEvent.deviceId = deviceIdJsVal->ToNumber<int32_t>();
289 }
290 auto targetDisplayIdJsVal = obj->GetProperty("targetDisplayId");
291 if (targetDisplayIdJsVal->IsNumber()) {
292 touchEvent.targetDisplayId = targetDisplayIdJsVal->ToNumber<int32_t>();
293 }
294 auto touchesJsVal = obj->GetProperty("touches");
295 if (touchesJsVal->IsArray()) {
296 JSRef<JSArray> touchesArray = JSRef<JSArray>::Cast(touchesJsVal);
297 for (auto index = 0; index < static_cast<int32_t>(touchesArray->Length()); index++) {
298 JSRef<JSVal> item = touchesArray->GetValueAt(index);
299 if (!item->IsObject()) {
300 continue;
301 }
302 JSRef<JSObject> itemObj = JSRef<JSObject>::Cast(item);
303 TouchPoint point;
304 point.id = itemObj->GetPropertyValue<int32_t>("id", 0);
305 point.x = itemObj->GetPropertyValue<float>("x", 0.0f);
306 point.y = itemObj->GetPropertyValue<float>("y", 0.0f);
307 point.screenX = itemObj->GetPropertyValue<float>("screenX", 0.0f);
308 point.screenY = itemObj->GetPropertyValue<float>("screenY", 0.0f);
309 point.originalId = itemObj->GetPropertyValue<int32_t>("id", 0);
310 touchEvent.pointers.emplace_back(point);
311 }
312 }
313 auto titleXJsVal = obj->GetProperty("tiltX");
314 if (titleXJsVal->IsNumber()) {
315 touchEvent.tiltX = titleXJsVal->ToNumber<float>();
316 }
317 auto titleYJsVal = obj->GetProperty("tiltY");
318 if (titleYJsVal->IsNumber()) {
319 touchEvent.tiltY = titleYJsVal->ToNumber<float>();
320 }
321 auto changedTouchesJsVal = obj->GetProperty("changedTouches");
322 if (changedTouchesJsVal->IsArray()) {
323 JSRef<JSArray> changedTouchesArray = JSRef<JSArray>::Cast(changedTouchesJsVal);
324 if (static_cast<int32_t>(changedTouchesArray->Length()) <= 0) {
325 TAG_LOGW(AceLogTag::ACE_INPUTKEYFLOW, "PostTouchEvent event changedTouchesArray is invalid");
326 info.SetReturnValue(JSRef<JSVal>::Make(ToJSValue(false)));
327 return touchEvent;
328 }
329 JSRef<JSVal> item = changedTouchesArray->GetValueAt(0);
330 if (!item->IsObject()) {
331 TAG_LOGW(AceLogTag::ACE_INPUTKEYFLOW, "PostTouchEvent event changedTouchesArray item is not an object");
332 info.SetReturnValue(JSRef<JSVal>::Make(ToJSValue(false)));
333 return touchEvent;
334 }
335 JSRef<JSObject> itemObj = JSRef<JSObject>::Cast(item);
336 touchEvent.id = itemObj->GetPropertyValue<int32_t>("id", 0);
337 touchEvent.x = itemObj->GetPropertyValue<float>("x", 0.0f);
338 touchEvent.y = itemObj->GetPropertyValue<float>("y", 0.0f);
339 touchEvent.screenX = itemObj->GetPropertyValue<float>("screenX", 0.0f);
340 touchEvent.screenY = itemObj->GetPropertyValue<float>("screenY", 0.0f);
341 touchEvent.originalId = itemObj->GetPropertyValue<int32_t>("id", 0);
342 }
343 return touchEvent;
344 }
345
UpdateStart(const JSCallbackInfo & info)346 void JSBaseNode::UpdateStart(const JSCallbackInfo& info)
347 {
348 scopedViewStackProcessor_ = std::make_unique<NG::ScopedViewStackProcessor>();
349 }
350
UpdateEnd(const JSCallbackInfo & info)351 void JSBaseNode::UpdateEnd(const JSCallbackInfo& info)
352 {
353 scopedViewStackProcessor_ = nullptr;
354 CHECK_NULL_VOID(viewNode_);
355 if (size_.IsValid()) {
356 viewNode_->SetParentLayoutConstraint(size_.ConvertToSizeT());
357 }
358 }
359
OnReuseWithBindThis(const JSCallbackInfo & info)360 void JSBaseNode::OnReuseWithBindThis(const JSCallbackInfo& info)
361 {
362 CHECK_NULL_VOID(realNode_);
363 std::queue<RefPtr<NG::UINode>> elements;
364 elements.push(realNode_);
365 void* data = static_cast<void*>(info.GetJsiRuntimeCallInfo());
366 while (!elements.empty()) {
367 auto currentNode = elements.front();
368 elements.pop();
369 if (!currentNode) {
370 continue;
371 }
372 if (AceType::InstanceOf<NG::CustomNodeBase>(currentNode)) {
373 auto customNode = AceType::DynamicCast<NG::CustomNodeBase>(currentNode);
374 customNode->FireOnReuseFunc(data);
375 } else {
376 for (const auto& child : currentNode->GetChildren()) {
377 elements.push(child);
378 }
379 }
380 }
381 }
382
OnRecycleWithBindThis(const JSCallbackInfo & info)383 void JSBaseNode::OnRecycleWithBindThis(const JSCallbackInfo& info)
384 {
385 CHECK_NULL_VOID(realNode_);
386 std::queue<RefPtr<NG::UINode>> elements;
387 elements.push(realNode_);
388 while (!elements.empty()) {
389 auto currentNode = elements.front();
390 elements.pop();
391 if (!currentNode) {
392 continue;
393 }
394 if (AceType::InstanceOf<NG::CustomNodeBase>(currentNode)) {
395 auto customNode = AceType::DynamicCast<NG::CustomNodeBase>(currentNode);
396 customNode->FireOnRecycleFunc();
397 } else {
398 for (const auto& child : currentNode->GetChildren()) {
399 elements.push(child);
400 }
401 }
402 }
403 }
404
JSBind(BindingTarget globalObj)405 void JSBaseNode::JSBind(BindingTarget globalObj)
406 {
407 JSClass<JSBaseNode>::Declare("__JSBaseNode__");
408
409 JSClass<JSBaseNode>::CustomMethod("create", &JSBaseNode::Create);
410 JSClass<JSBaseNode>::CustomMethod("finishUpdateFunc", &JSBaseNode::FinishUpdateFunc);
411 JSClass<JSBaseNode>::CustomMethod("postTouchEvent", &JSBaseNode::PostTouchEvent);
412 JSClass<JSBaseNode>::CustomMethod("disposeNode", &JSBaseNode::Dispose);
413 JSClass<JSBaseNode>::CustomMethod("updateStart", &JSBaseNode::UpdateStart);
414 JSClass<JSBaseNode>::CustomMethod("updateEnd", &JSBaseNode::UpdateEnd);
415 JSClass<JSBaseNode>::CustomMethod("onReuseWithBindObject", &JSBaseNode::OnReuseWithBindThis);
416 JSClass<JSBaseNode>::CustomMethod("onRecycleWithBindObject", &JSBaseNode::OnRecycleWithBindThis);
417
418 JSClass<JSBaseNode>::Bind(globalObj, JSBaseNode::ConstructorCallback, JSBaseNode::DestructorCallback);
419 }
420 } // namespace OHOS::Ace::Framework
421