1 /*
2 * Copyright (C) 2023-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 * Description: supply napi callback realization for interfaces.
15 * Author: zhangjingnan
16 * Create: 2023-4-11
17 */
18
19 #include <memory>
20 #include "cast_engine_log.h"
21 #include "napi_callback.h"
22 #include "napi_castengine_utils.h"
23
24 namespace OHOS {
25 namespace CastEngine {
26 namespace CastEngineClient {
27 DEFINE_CAST_ENGINE_LABEL("Cast-Napi-Callback");
28
NapiCallback(napi_env env)29 NapiCallback::NapiCallback(napi_env env) : env_(env)
30 {
31 if (env != nullptr) {
32 NAPI_CALL_RETURN_VOID(env, napi_get_uv_event_loop(env, &loop_));
33 }
34 }
35
~NapiCallback()36 NapiCallback::~NapiCallback()
37 {
38 CLOGD("no memory leak for queue-callback");
39 env_ = nullptr;
40 }
41
GetEnv() const42 napi_env NapiCallback::GetEnv() const
43 {
44 return env_;
45 }
46
AfterWorkCallback(std::shared_ptr<DataContext> context)47 void NapiCallback::AfterWorkCallback(std::shared_ptr<DataContext> context)
48 {
49 int argc = 0;
50 napi_handle_scope scope = nullptr;
51 napi_open_handle_scope(context->env, &scope);
52 napi_value argv[ARGC_MAX] = { nullptr };
53 if (context->getter) {
54 argc = ARGC_MAX;
55 context->getter(context->env, argc, argv);
56 }
57
58 napi_value undefined = nullptr;
59 if (napi_get_undefined(context->env, &undefined) != napi_ok) {
60 CLOGE("napi_get_undefined failed");
61 napi_close_handle_scope(context->env, scope);
62 return;
63 }
64 if (!IsCallbackValid(context->env, context->method, context->event)) {
65 CLOGE("callback is invalid event:%{public}d", context->event);
66 napi_close_handle_scope(context->env, scope);
67 return;
68 }
69 napi_value callback = nullptr;
70 if (napi_get_reference_value(context->env, context->method, &callback) != napi_ok) {
71 CLOGE("napi_get_reference_value failed");
72 ReleaseRef(context->env, context->method);
73 napi_close_handle_scope(context->env, scope);
74 return;
75 }
76 napi_value callResult = nullptr;
77 if (napi_call_function(context->env, undefined, callback, argc, argv, &callResult) != napi_ok) {
78 CLOGE("napi_call_function failed");
79 }
80 ReleaseRef(context->env, context->method);
81 napi_close_handle_scope(context->env, scope);
82 }
83
Call(napi_ref method,NapiArgsGetter & getter,int32_t event)84 void NapiCallback::Call(napi_ref method, NapiArgsGetter &getter, int32_t event)
85 {
86 CLOGD("Start to have JS call");
87 if (loop_ == nullptr) {
88 CLOGE("loop_ is nullptr");
89 return;
90 }
91 if (method == nullptr) {
92 CLOGE("method is nullptr");
93 return;
94 }
95
96 auto *work = new (std::nothrow) uv_work_t;
97 if (work == nullptr) {
98 CLOGE("no memory for uv_work_t");
99 return;
100 }
101 auto callback = shared_from_this();
102 work->data = new (std::nothrow) DataContext { env_, method, std::move(getter), callback, event };
103 if (work->data == nullptr) {
104 CLOGE("no memory for DataContext");
105 delete work;
106 return;
107 }
108 int res = uv_queue_work(loop_, work, [](uv_work_t *work) {}, [](uv_work_t *work, int status) {
109 std::shared_ptr<DataContext> context(static_cast<DataContext *>(work->data), [work](DataContext *ptr) {
110 delete ptr;
111 delete work;
112 });
113 if (!context->callback) {
114 CLOGE("callback is nullptr");
115 return;
116 }
117 context->callback->AfterWorkCallback(context);
118 });
119 if (res != 0) {
120 CLOGE("uv queue work failed");
121 delete static_cast<DataContext *>(work->data);
122 delete work;
123 return;
124 }
125 }
126
AddCallback(napi_env env,int32_t event,napi_value callback)127 napi_status NapiCallback::AddCallback(napi_env env, int32_t event, napi_value callback)
128 {
129 if ((event >= EVENT_TYPE_MAX) || (event < 0)) {
130 CLOGE("event %{public}d is invalid", event);
131 return napi_generic_failure;
132 }
133 CLOGI("Add callback %{public}d", event);
134 std::lock_guard<std::mutex> lockGuard(lock_);
135 constexpr int initialRefCount = 1;
136 napi_ref ref = nullptr;
137 if (GetRefByCallback(env, callbacks_[event], callback, ref) != napi_ok) {
138 CLOGE("get callback reference failed");
139 return napi_generic_failure;
140 }
141 if (ref != nullptr) {
142 CLOGD("callback has been registered");
143 return napi_ok;
144 }
145 napi_status status = napi_create_reference(env, callback, initialRefCount, &ref);
146 if (status != napi_ok) {
147 CLOGE("napi_create_reference failed");
148 return status;
149 }
150 callbacks_[event].push_back(ref);
151 return napi_ok;
152 }
153
RemoveCallback(napi_env env,int32_t event,napi_value callback)154 napi_status NapiCallback::RemoveCallback(napi_env env, int32_t event, napi_value callback)
155 {
156 if ((event >= EVENT_TYPE_MAX) || (event < 0)) {
157 CLOGE("event %{public}d is invalid", event);
158 return napi_generic_failure;
159 }
160 std::lock_guard<std::mutex> lockGuard(lock_);
161 if (callback == nullptr) {
162 for (auto &callbackRef : callbacks_[event]) {
163 ReleaseRef(env, callbackRef);
164 }
165 callbacks_[event].clear();
166 return napi_ok;
167 }
168 napi_ref ref = nullptr;
169 if (GetRefByCallback(env, callbacks_[event], callback, ref) != napi_ok) {
170 CLOGE("get callback reference failed");
171 return napi_generic_failure;
172 }
173 if (ref == nullptr) {
174 CLOGD("callback has been remove");
175 return napi_ok;
176 }
177 callbacks_[event].remove(ref);
178
179 return ReleaseRef(env, ref);
180 }
181
HandleEvent(int32_t event,NapiArgsGetter & getter)182 void NapiCallback::HandleEvent(int32_t event, NapiArgsGetter &getter)
183 {
184 if ((event >= EVENT_TYPE_MAX) || (event < 0)) {
185 CLOGE("event %{public}d is invalid", event);
186 return;
187 }
188 std::lock_guard<std::mutex> lockGuard(lock_);
189 if (callbacks_[event].empty()) {
190 CLOGE("not register callback event=%{public}d", event);
191 return;
192 }
193 for (auto ref = callbacks_[event].begin(); ref != callbacks_[event].end(); ++ref) {
194 Call(*ref, getter, event);
195 }
196 }
197
IsCallbackValid(napi_env env,napi_ref ref,int32_t event)198 bool NapiCallback::IsCallbackValid(napi_env env, napi_ref ref, int32_t event)
199 {
200 if ((event >= EVENT_TYPE_MAX) || (event < 0)) {
201 CLOGE("event %{public}d is invalid", event);
202 return false;
203 }
204 std::lock_guard<std::mutex> lockGuard(lock_);
205 for (auto &callbackRef : callbacks_[event]) {
206 if (callbackRef != ref) {
207 continue;
208 }
209 uint32_t refCount = 0xff;
210 napi_status ret = napi_reference_ref(env, ref, &refCount);
211 if (ret != napi_ok || refCount <= 1) {
212 CLOGE("callback is invalid ret: %{public}d refCount: %{public}d", ret, refCount);
213 return false;
214 }
215 return true;
216 }
217 CLOGE("callback has been remove");
218 return false;
219 }
220
IsCallbackListEmpty()221 bool NapiCallback::IsCallbackListEmpty()
222 {
223 std::lock_guard<std::mutex> lockGuard(lock_);
224 for (auto &callback : callbacks_) {
225 if (!callback.empty()) {
226 return false;
227 }
228 }
229 return true;
230 }
231
ReleaseRef(napi_env env,napi_ref ref)232 napi_status NapiCallback::ReleaseRef(napi_env env, napi_ref ref)
233 {
234 uint32_t refCount = 0xff;
235 napi_status ret = napi_ok;
236 napi_reference_unref(env, ref, &refCount);
237 if (refCount == 0) {
238 CLOGI("refCount is 0, delete callback reference");
239 ret = napi_delete_reference(env, ref);
240 }
241 if (ret != napi_ok) {
242 CLOGE("delete callback reference failed");
243 }
244 return ret;
245 }
246 } // namespace CastEngineClient
247 } // namespace CastEngine
248 } // namespace OHOS