1 /*
2 * Copyright (C) 2025 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 "JsObjectCache.h"
17
18 #include <meta/api/make_callback.h>
19 #include <meta/interface/intf_metadata.h>
20 #include <meta/interface/intf_task_queue_registry.h>
21 #include <meta/interface/property/construct_property.h>
22
23 #include <base/util/uid.h>
24 #include <core/plugin/intf_interface.h>
25
26 #include "TrueRootObject.h"
27
28 #if !defined(JSW_USE_TSF) || (!JSW_USE_TSF)
29 #include "nodejstaskqueue.h"
30 #endif
31
32 /**
33 * @brief Store a reference to a JS object in the metaobject.
34 */
35 class JSWrapperState : public CORE_NS::IInterface {
36 public:
37 static constexpr BASE_NS::Uid UID { "2ef39765-91f2-46c4-b85f-7cad40dd3bcd" };
38 const IInterface* GetInterface(const BASE_NS::Uid& uid) const override;
39 IInterface* GetInterface(const BASE_NS::Uid& uid) override;
40 void Ref() override;
41 void Unref() override;
42 JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name);
43 ~JSWrapperState();
44 NapiApi::Object GetObject();
45
46 private:
47 static void Call(napi_env env, napi_value js_callback, void* context, void* inData);
48 static void Final(napi_env env, void* finalize_data, void* finalize_hint);
49 BASE_NS::string name_;
50 volatile int32_t count_ { 0 };
51 napi_env env_ { nullptr };
52 napi_ref ref_ { nullptr };
53 #if JSW_USE_TSF
54 napi_threadsafe_function termfun { nullptr };
55 struct fun_parm {
56 napi_ref ref { nullptr };
57 napi_threadsafe_function termfun { nullptr };
58 };
59 #else
60 static bool DeleteReference(napi_ref);
61 #endif
62 };
63
GetInterface(const BASE_NS::Uid & uid) const64 const CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid) const
65 {
66 if (uid == CORE_NS::IInterface::UID) {
67 return this;
68 }
69 if (uid == JSWrapperState::UID) {
70 return this;
71 }
72 return nullptr;
73 }
74
GetInterface(const BASE_NS::Uid & uid)75 CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid)
76 {
77 if (uid == CORE_NS::IInterface::UID) {
78 return this;
79 }
80 if (uid == JSWrapperState::UID) {
81 return this;
82 }
83 return nullptr;
84 }
85
Ref()86 void JSWrapperState::Ref()
87 {
88 BASE_NS::AtomicIncrement(&count_);
89 }
90
Unref()91 void JSWrapperState::Unref()
92 {
93 if (BASE_NS::AtomicDecrement(&count_) == 0) {
94 delete this;
95 }
96 }
97
JSWrapperState(NapiApi::Object obj,BASE_NS::string_view name)98 JSWrapperState::JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name) : name_(name), env_(obj.GetEnv())
99 {
100 napi_status status;
101 // Create a WEAK reference to the object
102 status = napi_create_reference(env_, obj.ToNapiValue(), 0, &ref_);
103 #if JSW_USE_TSF
104 // Create a thread safe function to release the js reference, as the destructor MAY be called from engine thread.
105 napi_value jname = nullptr;
106 status = napi_create_string_latin1(env_, "JSW", NAPI_AUTO_LENGTH, &jname);
107 status = napi_create_threadsafe_function(
108 env_, nullptr, nullptr, jname, 0, 1, nullptr, &JSWrapperState::Final, nullptr, &JSWrapperState::Call, &termfun);
109 #else
110 // make sure nodejstaskqueue wont die before we are ready..
111 auto& tr = META_NS::GetTaskQueueRegistry();
112 auto jsQ = tr.GetTaskQueue(JS_THREAD_DEP);
113 auto queueRefCount = interface_cast<INodeJSTaskQueue>(jsQ);
114 queueRefCount->Acquire();
115 #endif
116 }
117
118 #if JSW_USE_TSF
Call(napi_env env,napi_value js_callback,void * context,void * inData)119 void JSWrapperState::Call(napi_env env, napi_value js_callback, void* context, void* inData)
120 {
121 napi_status status;
122 fun_parm* ref = (fun_parm*)inData;
123 status = napi_delete_reference(env, ref->ref);
124 status = napi_release_threadsafe_function(ref->termfun, napi_threadsafe_function_release_mode::napi_tsfn_release);
125 delete ref;
126 }
Final(napi_env env,void * finalize_data,void * context)127 void JSWrapperState::Final(napi_env env, void* finalize_data, void* context) {};
128 #else
129
DeleteReference(napi_ref ref)130 bool JSWrapperState::DeleteReference(napi_ref ref)
131 {
132 auto curQueue = META_NS::GetTaskQueueRegistry().GetCurrentTaskQueue();
133 if (auto p = interface_cast<INodeJSTaskQueue>(curQueue)) {
134 napi_env env = p->GetNapiEnv();
135 napi_status status = napi_delete_reference(env, ref);
136 p->Release(); // we don't need it anymore so release it.
137 }
138 return false;
139 }
140 #endif
141
~JSWrapperState()142 JSWrapperState::~JSWrapperState()
143 {
144 #if JSW_USE_TSF
145 napi_status status;
146 // trigger the threadsafe function, so that the javascript "weak reference" will be destroyed.
147 status = napi_call_threadsafe_function(
148 termfun, new fun_parm { ref_, termfun }, napi_threadsafe_function_call_mode::napi_tsfn_blocking);
149 #else
150 auto tq = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_THREAD_DEP);
151 auto curQueue = META_NS::GetTaskQueueRegistry().GetCurrentTaskQueue();
152 if (curQueue == tq) {
153 // in jsqueue, can call directly
154 // this is optional as the queue will handle it also "somewhat" directly in this case
155 DeleteReference(ref_);
156 return;
157 }
158 if (auto p = interface_cast<INodeJSTaskQueue>(tq)) {
159 auto tok = tq->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(DeleteReference, ref_));
160 if (!tok) {
161 LOG_F("Cleanup task add failed!");
162 }
163 } else {
164 LOG_F("NODE JS TASK QUEUE IS NOT INITIALIZED OR INVALID TYPE");
165 }
166
167 #endif
168 }
169
GetObject()170 NapiApi::Object JSWrapperState::GetObject()
171 {
172 napi_status status;
173 napi_value value;
174 status = napi_get_reference_value(env_, ref_, &value);
175 return { env_, value };
176 }
177
FetchJsObj(const META_NS::IObject::Ptr & obj,BASE_NS::string_view name)178 NapiApi::Object FetchJsObj(const META_NS::IObject::Ptr& obj, BASE_NS::string_view name)
179 {
180 using namespace META_NS;
181
182 // access hidden property.
183 if (auto AppMeta = interface_pointer_cast<IMetadata>(obj)) {
184 if (auto wrapper = AppMeta->GetProperty<SharedPtrIInterface>(name, MetadataQuery::EXISTING)) {
185 // The native object already contains a JS object.
186 return interface_cast<JSWrapperState>(wrapper->GetValue())->GetObject();
187 }
188 }
189 return {};
190 }
191
StoreJsObj(const META_NS::IObject::Ptr & obj,const NapiApi::Object & jsobj,BASE_NS::string_view name)192 NapiApi::Object StoreJsObj(const META_NS::IObject::Ptr& obj, const NapiApi::Object& jsobj, BASE_NS::string_view name)
193 {
194 using namespace META_NS;
195 if (auto AppMeta = interface_pointer_cast<IMetadata>(obj)) {
196 // Add a reference to the JS object to the native object.
197 const auto& obr = GetObjectRegistry();
198 auto wrapper = AppMeta->GetProperty<SharedPtrIInterface>(name, META_NS::MetadataQuery::EXISTING);
199 // check if the property exists.
200 if (wrapper) {
201 // .. does the wrapper exist? (ie. reference from native to js)
202 if (auto val = interface_cast<JSWrapperState>(wrapper->GetValue())) {
203 // validate it
204 auto ref = val->GetObject();
205 bool equals = false;
206 if (ref) {
207 // we have ref.. so make the compare here
208 napi_strict_equals(jsobj.GetEnv(), jsobj.ToNapiValue(), ref.ToNapiValue(), &equals);
209 if (!equals) {
210 // this may be a problem
211 // (there should only be a 1-1 mapping between js objects and native objects)
212 LOG_F("_JSW exists and points to different object!");
213 } else {
214 // objects already linked
215 return ref;
216 }
217 } else {
218 // creating a new jsobject for existing native object that was bound before (but js object was GC:d)
219 // this is fine.
220 LOG_V("Rewrapping an object! (creating a new js object to replace a GC'd one for a native object)");
221 }
222 }
223 } else {
224 // create the wrapper property
225 wrapper = ConstructProperty<SharedPtrIInterface>(
226 name, nullptr, ObjectFlagBits::INTERNAL | ObjectFlagBits::NATIVE);
227 AppMeta->AddProperty(wrapper);
228 }
229 // link native to js.
230 auto res = BASE_NS::shared_ptr<CORE_NS::IInterface>(new JSWrapperState(jsobj, obj->GetClassName()));
231 wrapper->SetValue(res);
232 return interface_cast<JSWrapperState>(res)->GetObject();
233 }
234 napi_value val;
235 napi_get_null(jsobj.GetEnv(), &val);
236 return { jsobj.GetEnv(), val };
237 }
238
DebugNativesHavingJS()239 void DebugNativesHavingJS()
240 {
241 LOG_F("Dump MetaObjects");
242 for (auto&& v : META_NS::GetObjectRegistry().GetAllObjectInstances()) {
243 if (auto i = interface_cast<META_NS::IMetadata>(v)) {
244 auto II = interface_cast<META_NS::IObject>(i);
245 BASE_NS::string classname;
246 if (II) {
247 classname = II->GetClassName();
248 }
249 auto p = i->GetProperty<META_NS::SharedPtrIInterface>("_JSW", META_NS::MetadataQuery::EXISTING);
250 if (!p) {
251 p = i->GetProperty<META_NS::SharedPtrIInterface>("_JSWMesh", META_NS::MetadataQuery::EXISTING);
252 }
253 if (p) {
254 LOG_F("jsobj: %s %s (strong count %u) (has JSW)", classname.c_str(), v->GetName().c_str(),
255 v.use_count());
256
257 if (auto val = interface_cast<JSWrapperState>(p->GetValue())) {
258 if (auto ref = val->GetObject()) {
259 LOG_F("\tJS object still alive.");
260 if (auto* tro = ref.GetRoot()) {
261 auto nat = tro->GetNativeObject();
262 if (!nat) {
263 LOG_F("\tand is wrapped but has no native object.");
264 } else {
265 if (interface_cast<CORE_NS::IInterface>(nat) !=
266 interface_cast<CORE_NS::IInterface>(v)) {
267 LOG_F("\t** JS OBJECT POINTS TO DIFFERENT NATIVE OBJECT! **");
268
269 if (auto m = tro->GetNativeObject()) {
270 auto JI = interface_cast<META_NS::IObject>(m);
271 if (JI != II) {
272 BASE_NS::string classname;
273 if (JI) {
274 classname = JI->GetClassName();
275 }
276 LOG_F("but the _JSW points to another object?: %s %s (strong count %u) "
277 "strong: %d",
278 classname.c_str(), m->GetName().c_str(), m.use_count(),
279 tro->IsStrong());
280 }
281 }
282 } else {
283 LOG_F("\t and correctly links to this native object.");
284 }
285 }
286 } else {
287 LOG_F("\t.. but is unwrapped (null tro).");
288 }
289 } else {
290 LOG_F("Javascript object already dead.");
291 }
292 }
293 } else {
294 LOG_F("obj: %s %s (strong count %u)", classname.c_str(), v->GetName().c_str(),
295 v.use_count());
296 }
297 }
298 }
299 }