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 "napi_remote_object_holder.h"
17 
18 #include <uv.h>
19 #include <string_ex.h>
20 #include "ipc_debug.h"
21 #include "log_tags.h"
22 
23 namespace OHOS {
24 static constexpr OHOS::HiviewDFX::HiLogLabel LOG_LABEL = { LOG_CORE, LOG_ID_IPC_NAPI, "napi_remoteObject_holder" };
25 
OnEnvCleanUp(void * data)26 static void OnEnvCleanUp(void *data)
27 {
28     if (data == nullptr) {
29         ZLOGE(LOG_LABEL, "data is null");
30         return;
31     }
32     NAPIRemoteObjectHolder *holder = reinterpret_cast<NAPIRemoteObjectHolder *>(data);
33     // js env has been destrcted, clear saved env info, and check befor use it
34     holder->CleanJsEnv();
35 }
36 
NAPIRemoteObjectHolder(napi_env env,const std::u16string & descriptor,napi_value thisVar)37 NAPIRemoteObjectHolder::NAPIRemoteObjectHolder(napi_env env, const std::u16string &descriptor, napi_value thisVar)
38     : env_(env), descriptor_(descriptor), sptrCachedObject_(nullptr), wptrCachedObject_(nullptr),
39       localInterfaceRef_(nullptr), attachCount_(1), jsObjectRef_(nullptr)
40 {
41     jsThreadId_ = std::this_thread::get_id();
42     // create weak ref, need call napi_delete_reference to release memory,
43     // increase ref count when the JS object will transfer to another thread or process.
44     napi_create_reference(env, thisVar, 0, &jsObjectRef_);
45 
46     // register listener for env destruction
47     napi_status status = napi_add_env_cleanup_hook(env, OnEnvCleanUp, this);
48     if (status != napi_ok) {
49         ZLOGE(LOG_LABEL, "add cleanup hook failed");
50     }
51 }
52 
DeleteJsObjectRefInUvWork()53 void NAPIRemoteObjectHolder::DeleteJsObjectRefInUvWork()
54 {
55     if (env_ == nullptr) {
56         ZLOGE(LOG_LABEL, "js env has been destructed");
57         return;
58     }
59     uv_loop_s *loop = nullptr;
60     napi_get_uv_event_loop(env_, &loop);
61     if (loop == nullptr) {
62         ZLOGE(LOG_LABEL, "loop is nullptr");
63         return;
64     }
65     uv_work_t *work = new (std::nothrow) uv_work_t;
66     if (work == nullptr) {
67         ZLOGE(LOG_LABEL, "failed to new work");
68         return;
69     }
70     OperateJsRefParam *param = new (std::nothrow) OperateJsRefParam {
71         .env = env_,
72         .thisVarRef = jsObjectRef_
73     };
74     if (param == nullptr) {
75         ZLOGE(LOG_LABEL, "new OperateJsRefParam failed");
76         delete work;
77         return;
78     }
79     work->data = reinterpret_cast<void *>(param);
80     int uvRet = uv_queue_work(loop, work, [](uv_work_t *work) {
81         ZLOGD(LOG_LABEL, "enter work pool.");
82     }, [](uv_work_t *work, int status) {
83         OperateJsRefParam *param = reinterpret_cast<OperateJsRefParam *>(work->data);
84         napi_handle_scope scope = nullptr;
85         napi_open_handle_scope(param->env, &scope);
86         napi_status napiStatus = napi_delete_reference(param->env, param->thisVarRef);
87         if (napiStatus != napi_ok) {
88             ZLOGE(LOG_LABEL, "failed to delete ref on uv work");
89         }
90         napi_close_handle_scope(param->env, scope);
91         delete param;
92         delete work;
93     });
94     if (uvRet != 0) {
95         delete param;
96         delete work;
97         ZLOGE(LOG_LABEL, "uv_queue_work failed, ret %{public}d", uvRet);
98     }
99 }
100 
~NAPIRemoteObjectHolder()101 NAPIRemoteObjectHolder::~NAPIRemoteObjectHolder()
102 {
103     if (env_ == nullptr) {
104         ZLOGE(LOG_LABEL, "js env has been destructed");
105         return;
106     }
107 
108     napi_status status = napi_remove_env_cleanup_hook(env_, OnEnvCleanUp, this);
109     if (status != napi_ok) {
110         ZLOGE(LOG_LABEL, "remove cleanup hook failed");
111     }
112 
113     if (localInterfaceRef_ != nullptr) {
114         status = napi_delete_reference(env_, localInterfaceRef_);
115         if (status != napi_ok) {
116             ZLOGE(LOG_LABEL, "failed to delete ref");
117         }
118     }
119 
120     if (jsObjectRef_ != nullptr) {
121         if (jsThreadId_ == std::this_thread::get_id()) {
122             status = napi_delete_reference(env_, jsObjectRef_);
123             if (status != napi_ok) {
124                 ZLOGE(LOG_LABEL, "failed to delete ref");
125             }
126         } else {
127             DeleteJsObjectRefInUvWork();
128         }
129     }
130 }
131 
Get()132 sptr<IRemoteObject> NAPIRemoteObjectHolder::Get()
133 {
134     std::lock_guard<std::mutex> lockGuard(mutex_);
135     // grab an strong reference to the object,
136     // so it will not be freed util this reference released.
137     if (sptrCachedObject_ != nullptr) {
138         return sptrCachedObject_;
139     }
140 
141     sptr<IRemoteObject> tmp = wptrCachedObject_.promote();
142     if (tmp == nullptr && env_ != nullptr) {
143         tmp = new (std::nothrow) NAPIRemoteObject(jsThreadId_, env_, jsObjectRef_, descriptor_);
144         if (tmp == nullptr) {
145             ZLOGE(LOG_LABEL, "new NAPIRemoteObject failed");
146             return nullptr;
147         }
148         wptrCachedObject_ = tmp;
149     }
150     return tmp;
151 }
152 
Set(sptr<IRemoteObject> object)153 void NAPIRemoteObjectHolder::Set(sptr<IRemoteObject> object)
154 {
155     std::lock_guard<std::mutex> lockGuard(mutex_);
156     IPCObjectStub *tmp = static_cast<IPCObjectStub *>(object.GetRefPtr());
157     if (tmp->GetObjectType() == IPCObjectStub::OBJECT_TYPE_JAVASCRIPT) {
158         wptrCachedObject_ = object;
159     } else {
160         sptrCachedObject_ = object;
161     }
162 }
163 
GetJsObjectRef() const164 napi_ref NAPIRemoteObjectHolder::GetJsObjectRef() const
165 {
166     return jsObjectRef_;
167 }
168 
GetJsObjectEnv() const169 napi_env NAPIRemoteObjectHolder::GetJsObjectEnv() const
170 {
171     return env_;
172 }
173 
CleanJsEnv()174 void NAPIRemoteObjectHolder::CleanJsEnv()
175 {
176     env_ = nullptr;
177     jsObjectRef_ = nullptr;
178     sptr<IRemoteObject> tmp = wptrCachedObject_.promote();
179     if (tmp != nullptr) {
180         NAPIRemoteObject *object = static_cast<NAPIRemoteObject *>(tmp.GetRefPtr());
181         ZLOGI(LOG_LABEL, "reset env and napi_ref");
182         object->ResetJsEnv();
183     }
184 }
185 
attachLocalInterface(napi_value localInterface,std::string & descriptor)186 void NAPIRemoteObjectHolder::attachLocalInterface(napi_value localInterface, std::string &descriptor)
187 {
188     if (env_ == nullptr) {
189         ZLOGE(LOG_LABEL, "Js env has been destructed");
190         return;
191     }
192     if (localInterfaceRef_ != nullptr) {
193         napi_delete_reference(env_, localInterfaceRef_);
194     }
195     napi_create_reference(env_, localInterface, 0, &localInterfaceRef_);
196     descriptor_ = Str8ToStr16(descriptor);
197 }
198 
queryLocalInterface(std::string & descriptor)199 napi_value NAPIRemoteObjectHolder::queryLocalInterface(std::string &descriptor)
200 {
201     if (env_ == nullptr) {
202         ZLOGE(LOG_LABEL, "Js env has been destructed");
203         return nullptr;
204     }
205     if (!descriptor_.empty() && strcmp(Str16ToStr8(descriptor_).c_str(), descriptor.c_str()) == 0) {
206         napi_value ret = nullptr;
207         napi_get_reference_value(env_, localInterfaceRef_, &ret);
208         return ret;
209     }
210     napi_value result = nullptr;
211     napi_get_null(env_, &result);
212     return result;
213 }
214 } // namespace OHOS