/* * Copyright (C) 2025 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "JsObjectCache.h" #include #include #include #include #include #include #include "TrueRootObject.h" #if !defined(JSW_USE_TSF) || (!JSW_USE_TSF) #include "nodejstaskqueue.h" #endif /** * @brief Store a reference to a JS object in the metaobject. */ class JSWrapperState : public CORE_NS::IInterface { public: static constexpr BASE_NS::Uid UID { "2ef39765-91f2-46c4-b85f-7cad40dd3bcd" }; const IInterface* GetInterface(const BASE_NS::Uid& uid) const override; IInterface* GetInterface(const BASE_NS::Uid& uid) override; void Ref() override; void Unref() override; JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name); ~JSWrapperState(); NapiApi::Object GetObject(); private: static void Call(napi_env env, napi_value js_callback, void* context, void* inData); static void Final(napi_env env, void* finalize_data, void* finalize_hint); BASE_NS::string name_; volatile int32_t count_ { 0 }; napi_env env_ { nullptr }; napi_ref ref_ { nullptr }; #if JSW_USE_TSF napi_threadsafe_function termfun { nullptr }; struct fun_parm { napi_ref ref { nullptr }; napi_threadsafe_function termfun { nullptr }; }; #else static bool DeleteReference(napi_ref); #endif }; const CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid) const { if (uid == CORE_NS::IInterface::UID) { return this; } if (uid == JSWrapperState::UID) { return this; } return nullptr; } CORE_NS::IInterface* JSWrapperState::GetInterface(const BASE_NS::Uid& uid) { if (uid == CORE_NS::IInterface::UID) { return this; } if (uid == JSWrapperState::UID) { return this; } return nullptr; } void JSWrapperState::Ref() { BASE_NS::AtomicIncrement(&count_); } void JSWrapperState::Unref() { if (BASE_NS::AtomicDecrement(&count_) == 0) { delete this; } } JSWrapperState::JSWrapperState(NapiApi::Object obj, BASE_NS::string_view name) : name_(name), env_(obj.GetEnv()) { napi_status status; // Create a WEAK reference to the object status = napi_create_reference(env_, obj.ToNapiValue(), 0, &ref_); #if JSW_USE_TSF // Create a thread safe function to release the js reference, as the destructor MAY be called from engine thread. napi_value jname = nullptr; status = napi_create_string_latin1(env_, "JSW", NAPI_AUTO_LENGTH, &jname); status = napi_create_threadsafe_function( env_, nullptr, nullptr, jname, 0, 1, nullptr, &JSWrapperState::Final, nullptr, &JSWrapperState::Call, &termfun); #else // make sure nodejstaskqueue wont die before we are ready.. auto& tr = META_NS::GetTaskQueueRegistry(); auto jsQ = tr.GetTaskQueue(JS_THREAD_DEP); auto queueRefCount = interface_cast(jsQ); queueRefCount->Acquire(); #endif } #if JSW_USE_TSF void JSWrapperState::Call(napi_env env, napi_value js_callback, void* context, void* inData) { napi_status status; fun_parm* ref = (fun_parm*)inData; status = napi_delete_reference(env, ref->ref); status = napi_release_threadsafe_function(ref->termfun, napi_threadsafe_function_release_mode::napi_tsfn_release); delete ref; } void JSWrapperState::Final(napi_env env, void* finalize_data, void* context) {}; #else bool JSWrapperState::DeleteReference(napi_ref ref) { auto curQueue = META_NS::GetTaskQueueRegistry().GetCurrentTaskQueue(); if (auto p = interface_cast(curQueue)) { napi_env env = p->GetNapiEnv(); napi_status status = napi_delete_reference(env, ref); p->Release(); // we don't need it anymore so release it. } return false; } #endif JSWrapperState::~JSWrapperState() { #if JSW_USE_TSF napi_status status; // trigger the threadsafe function, so that the javascript "weak reference" will be destroyed. status = napi_call_threadsafe_function( termfun, new fun_parm { ref_, termfun }, napi_threadsafe_function_call_mode::napi_tsfn_blocking); #else auto tq = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_THREAD_DEP); auto curQueue = META_NS::GetTaskQueueRegistry().GetCurrentTaskQueue(); if (curQueue == tq) { // in jsqueue, can call directly // this is optional as the queue will handle it also "somewhat" directly in this case DeleteReference(ref_); return; } if (auto p = interface_cast(tq)) { auto tok = tq->AddTask(META_NS::MakeCallback(DeleteReference, ref_)); if (!tok) { LOG_F("Cleanup task add failed!"); } } else { LOG_F("NODE JS TASK QUEUE IS NOT INITIALIZED OR INVALID TYPE"); } #endif } NapiApi::Object JSWrapperState::GetObject() { napi_status status; napi_value value; status = napi_get_reference_value(env_, ref_, &value); return { env_, value }; } NapiApi::Object FetchJsObj(const META_NS::IObject::Ptr& obj, BASE_NS::string_view name) { using namespace META_NS; // access hidden property. if (auto AppMeta = interface_pointer_cast(obj)) { if (auto wrapper = AppMeta->GetProperty(name, MetadataQuery::EXISTING)) { // The native object already contains a JS object. return interface_cast(wrapper->GetValue())->GetObject(); } } return {}; } NapiApi::Object StoreJsObj(const META_NS::IObject::Ptr& obj, const NapiApi::Object& jsobj, BASE_NS::string_view name) { using namespace META_NS; if (auto AppMeta = interface_pointer_cast(obj)) { // Add a reference to the JS object to the native object. const auto& obr = GetObjectRegistry(); auto wrapper = AppMeta->GetProperty(name, META_NS::MetadataQuery::EXISTING); // check if the property exists. if (wrapper) { // .. does the wrapper exist? (ie. reference from native to js) if (auto val = interface_cast(wrapper->GetValue())) { // validate it auto ref = val->GetObject(); bool equals = false; if (ref) { // we have ref.. so make the compare here napi_strict_equals(jsobj.GetEnv(), jsobj.ToNapiValue(), ref.ToNapiValue(), &equals); if (!equals) { // this may be a problem // (there should only be a 1-1 mapping between js objects and native objects) LOG_F("_JSW exists and points to different object!"); } else { // objects already linked return ref; } } else { // creating a new jsobject for existing native object that was bound before (but js object was GC:d) // this is fine. LOG_V("Rewrapping an object! (creating a new js object to replace a GC'd one for a native object)"); } } } else { // create the wrapper property wrapper = ConstructProperty( name, nullptr, ObjectFlagBits::INTERNAL | ObjectFlagBits::NATIVE); AppMeta->AddProperty(wrapper); } // link native to js. auto res = BASE_NS::shared_ptr(new JSWrapperState(jsobj, obj->GetClassName())); wrapper->SetValue(res); return interface_cast(res)->GetObject(); } napi_value val; napi_get_null(jsobj.GetEnv(), &val); return { jsobj.GetEnv(), val }; } void DebugNativesHavingJS() { LOG_F("Dump MetaObjects"); for (auto&& v : META_NS::GetObjectRegistry().GetAllObjectInstances()) { if (auto i = interface_cast(v)) { auto II = interface_cast(i); BASE_NS::string classname; if (II) { classname = II->GetClassName(); } auto p = i->GetProperty("_JSW", META_NS::MetadataQuery::EXISTING); if (!p) { p = i->GetProperty("_JSWMesh", META_NS::MetadataQuery::EXISTING); } if (p) { LOG_F("jsobj: %s %s (strong count %u) (has JSW)", classname.c_str(), v->GetName().c_str(), v.use_count()); if (auto val = interface_cast(p->GetValue())) { if (auto ref = val->GetObject()) { LOG_F("\tJS object still alive."); if (auto* tro = ref.GetRoot()) { auto nat = tro->GetNativeObject(); if (!nat) { LOG_F("\tand is wrapped but has no native object."); } else { if (interface_cast(nat) != interface_cast(v)) { LOG_F("\t** JS OBJECT POINTS TO DIFFERENT NATIVE OBJECT! **"); if (auto m = tro->GetNativeObject()) { auto JI = interface_cast(m); if (JI != II) { BASE_NS::string classname; if (JI) { classname = JI->GetClassName(); } LOG_F("but the _JSW points to another object?: %s %s (strong count %u) " "strong: %d", classname.c_str(), m->GetName().c_str(), m.use_count(), tro->IsStrong()); } } } else { LOG_F("\t and correctly links to this native object."); } } } else { LOG_F("\t.. but is unwrapped (null tro)."); } } else { LOG_F("Javascript object already dead."); } } } else { LOG_F("obj: %s %s (strong count %u)", classname.c_str(), v->GetName().c_str(), v.use_count()); } } } }