1 /*
2 * Copyright (c) 2021-2022 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 <algorithm>
16 #include <map>
17 #include <mutex>
18 #include <set>
19
20 #include "napi/native_api.h"
21 #include "napi/native_common.h"
22 #include "napi/native_node_api.h"
23
24 #include "base/log/log.h"
25 #include "bridge/common/media_query/media_queryer.h"
26 #include "bridge/common/utils/engine_helper.h"
27 #include "bridge/js_frontend/engine/common/js_engine.h"
28
29 namespace OHOS::Ace::Napi {
30 namespace {
31 constexpr size_t STR_BUFFER_SIZE = 1024;
32 }
33
34 using namespace OHOS::Ace::Framework;
35 struct MediaQueryResult {
36 bool matches_ = false;
37 std::string media_;
38
MediaQueryResultOHOS::Ace::Napi::MediaQueryResult39 MediaQueryResult(bool match, const std::string& media) : matches_(match), media_(media) {}
40 virtual ~MediaQueryResult() = default;
NapiSerializerOHOS::Ace::Napi::MediaQueryResult41 virtual void NapiSerializer(napi_env& env, napi_value& result)
42 {
43 /* construct a MediaQueryListener object */
44 napi_create_object(env, &result);
45 napi_handle_scope scope = nullptr;
46 napi_open_handle_scope(env, &scope);
47 if (scope == nullptr) {
48 return;
49 }
50
51 napi_value matchesVal = nullptr;
52 napi_get_boolean(env, matches_, &matchesVal);
53 napi_set_named_property(env, result, "matches", matchesVal);
54
55 napi_value mediaVal = nullptr;
56 napi_create_string_utf8(env, media_.c_str(), media_.size(), &mediaVal);
57 napi_set_named_property(env, result, "media", mediaVal);
58 napi_close_handle_scope(env, scope);
59 }
60 };
61
62 class MediaQueryListener : public MediaQueryResult {
63 public:
MediaQueryListener(bool match,const std::string & media)64 MediaQueryListener(bool match, const std::string& media) : MediaQueryResult(match, media) {}
~MediaQueryListener()65 ~MediaQueryListener() override
66 {
67 const std::lock_guard<std::mutex> lock(mutex_);
68 auto iter = listenerSets_.begin();
69 while (iter != listenerSets_.end()) {
70 iter->second.erase(this);
71 if (iter->second.empty()) {
72 iter->first->UnregisterMediaUpdateCallback();
73 iter = listenerSets_.erase(iter);
74 } else {
75 iter++;
76 }
77 }
78
79 if (env_ == nullptr) {
80 return;
81 }
82 for (auto& item : cbList_) {
83 napi_delete_reference(env_, item);
84 }
85 if (thisVarRef_ != nullptr) {
86 napi_delete_reference(env_, thisVarRef_);
87 }
88 }
89
NapiCallback(JsEngine * jsEngine)90 static void NapiCallback(JsEngine* jsEngine)
91 {
92 const std::lock_guard<std::mutex> lock(mutex_);
93 MediaQueryer queryer;
94 for (auto listener : listenerSets_[jsEngine]) {
95 auto json = MediaQueryInfo::GetMediaQueryJsonInfo();
96 listener->matches_ = queryer.MatchCondition(listener->media_, json);
97 for (auto& cbRef : listener->cbList_) {
98 napi_handle_scope scope = nullptr;
99 napi_open_handle_scope(listener->env_, &scope);
100 if (scope == nullptr) {
101 return;
102 }
103 napi_value thisVal = nullptr;
104 napi_get_reference_value(listener->env_, listener->thisVarRef_, &thisVal);
105
106 napi_value cb = nullptr;
107 napi_get_reference_value(listener->env_, cbRef, &cb);
108
109 napi_value resultArg = nullptr;
110 listener->MediaQueryResult::NapiSerializer(listener->env_, resultArg);
111
112 napi_value result = nullptr;
113 LOGI("NAPI MediaQueryCallback call js");
114 napi_call_function(listener->env_, thisVal, cb, 1, &resultArg, &result);
115 napi_close_handle_scope(listener->env_, scope);
116 }
117 }
118 }
119
On(napi_env env,napi_callback_info info)120 static napi_value On(napi_env env, napi_callback_info info)
121 {
122 LOGI("NAPI MediaQuery On called");
123 auto jsEngine = EngineHelper::GetCurrentEngine();
124 if (!jsEngine) {
125 LOGE("get jsEngine failed");
126 return nullptr;
127 }
128 jsEngine->RegisterMediaUpdateCallback(NapiCallback);
129
130 napi_handle_scope scope = nullptr;
131 napi_open_handle_scope(env, &scope);
132 if (scope == nullptr) {
133 return nullptr;
134 }
135 napi_value thisVar = nullptr;
136 napi_value cb = nullptr;
137 size_t argc = ParseArgs(env, info, thisVar, cb);
138 NAPI_ASSERT(env, (argc == 2 && thisVar != nullptr && cb != nullptr), "Invalid arguments");
139
140 MediaQueryListener* listener = GetListener(env, thisVar);
141 if (!listener) {
142 LOGE("listener is null");
143 napi_close_handle_scope(env, scope);
144 return nullptr;
145 }
146 auto iter = listener->FindCbList(cb);
147 if (iter != listener->cbList_.end()) {
148 napi_close_handle_scope(env, scope);
149 return nullptr;
150 }
151 napi_ref ref = nullptr;
152 napi_create_reference(env, cb, 1, &ref);
153 listener->cbList_.emplace_back(ref);
154 napi_close_handle_scope(env, scope);
155
156 #if defined(PREVIEW)
157 NapiCallback(AceType::RawPtr(jsEngine));
158 #endif
159
160 return nullptr;
161 }
162
Off(napi_env env,napi_callback_info info)163 static napi_value Off(napi_env env, napi_callback_info info)
164 {
165 napi_value thisVar = nullptr;
166 napi_value cb = nullptr;
167 size_t argc = ParseArgs(env, info, thisVar, cb);
168 MediaQueryListener* listener = GetListener(env, thisVar);
169 if (!listener) {
170 LOGE("listener is null");
171 return nullptr;
172 }
173 if (argc == 1) {
174 for (auto& item : listener->cbList_) {
175 napi_delete_reference(listener->env_, item);
176 }
177 listener->cbList_.clear();
178 } else {
179 NAPI_ASSERT(env, (argc == 2 && listener != nullptr && cb != nullptr), "Invalid arguments");
180 auto iter = listener->FindCbList(cb);
181 if (iter != listener->cbList_.end()) {
182 napi_delete_reference(listener->env_, *iter);
183 listener->cbList_.erase(iter);
184 }
185 }
186 auto jsEngine = EngineHelper::GetCurrentEngine();
187 if (!jsEngine) {
188 LOGE("get jsEngine failed");
189 return nullptr;
190 }
191 const std::lock_guard<std::mutex> lock(mutex_);
192 if (listenerSets_[AceType::RawPtr(jsEngine)].empty()) {
193 jsEngine->UnregisterMediaUpdateCallback();
194 listenerSets_.erase(AceType::RawPtr(jsEngine));
195 }
196 return nullptr;
197 }
198
FindCbList(napi_value cb)199 std::list<napi_ref>::iterator FindCbList(napi_value cb)
200 {
201 return std::find_if(cbList_.begin(), cbList_.end(), [env = env_, cb](const napi_ref& item) -> bool {
202 bool result = false;
203 napi_value refItem;
204 napi_get_reference_value(env, item, &refItem);
205 napi_strict_equals(env, refItem, cb, &result);
206 return result;
207 });
208 }
209
NapiSerializer(napi_env & env,napi_value & result)210 void NapiSerializer(napi_env& env, napi_value& result) override
211 {
212 MediaQueryResult::NapiSerializer(env, result);
213
214 napi_wrap(
215 env, result, this,
216 [](napi_env env, void* data, void* hint) {
217 MediaQueryListener* listener = static_cast<MediaQueryListener*>(data);
218 if (listener != nullptr) {
219 delete listener;
220 }
221 },
222 nullptr, nullptr);
223
224 /* insert callback functions */
225 const char* funName = "on";
226 napi_value funcValue = nullptr;
227 napi_create_function(env, funName, NAPI_AUTO_LENGTH, On, nullptr, &funcValue);
228 napi_set_named_property(env, result, funName, funcValue);
229
230 funName = "off";
231 napi_create_function(env, funName, NAPI_AUTO_LENGTH, Off, nullptr, &funcValue);
232 napi_set_named_property(env, result, funName, funcValue);
233 }
234
235 private:
Initialize(napi_env env,napi_value thisVar)236 void Initialize(napi_env env, napi_value thisVar)
237 {
238 napi_handle_scope scope = nullptr;
239 napi_open_handle_scope(env, &scope);
240 if (scope == nullptr) {
241 return;
242 }
243 if (env_ == nullptr) {
244 env_ = env;
245 }
246 if (thisVarRef_ == nullptr) {
247 napi_create_reference(env, thisVar, 1, &thisVarRef_);
248 }
249 napi_close_handle_scope(env, scope);
250 auto jsEngine = EngineHelper::GetCurrentEngine();
251 if (!jsEngine) {
252 LOGE("get jsEngine failed");
253 return;
254 }
255 const std::lock_guard<std::mutex> lock(mutex_);
256 listenerSets_[AceType::RawPtr(jsEngine)].emplace(this);
257 }
258
GetListener(napi_env env,napi_value thisVar)259 static MediaQueryListener* GetListener(napi_env env, napi_value thisVar)
260 {
261 MediaQueryListener* listener = nullptr;
262 napi_unwrap(env, thisVar, (void**)&listener);
263 listener->Initialize(env, thisVar);
264 return listener;
265 }
266
ParseArgs(napi_env & env,napi_callback_info & info,napi_value & thisVar,napi_value & cb)267 static size_t ParseArgs(napi_env& env, napi_callback_info& info, napi_value& thisVar, napi_value& cb)
268 {
269 const size_t argNum = 2;
270 size_t argc = argNum;
271 napi_value argv[argNum] = { 0 };
272 void* data = nullptr;
273 napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
274 NAPI_ASSERT_BASE(env, argc > 0, "too few parameter", 0);
275
276 napi_valuetype napiType;
277 NAPI_CALL_BASE(env, napi_typeof(env, argv[0], &napiType), 0);
278 NAPI_ASSERT_BASE(env, napiType == napi_string, "parameter 1 should be string", 0);
279 char type[STR_BUFFER_SIZE] = { 0 };
280 size_t len = 0;
281 napi_get_value_string_utf8(env, argv[0], type, STR_BUFFER_SIZE, &len);
282 NAPI_ASSERT_BASE(env, len < STR_BUFFER_SIZE, "condition string too long", 0);
283 NAPI_ASSERT_BASE(env, strcmp("change", type) == 0, "type mismatch('change')", 0);
284
285 if (argc <= 1) {
286 return argc;
287 }
288
289 NAPI_CALL_BASE(env, napi_typeof(env, argv[1], &napiType), 0);
290 NAPI_ASSERT_BASE(env, napiType == napi_function, "type mismatch for parameter 2", 0);
291 cb = argv[1];
292 return argc;
293 }
294
295 napi_ref thisVarRef_ = nullptr;
296 napi_env env_ = nullptr;
297 std::list<napi_ref> cbList_;
298 static std::map<JsEngine*, std::set<MediaQueryListener*>> listenerSets_;
299 static std::mutex mutex_;
300 };
301 std::map<JsEngine*, std::set<MediaQueryListener*>> MediaQueryListener::listenerSets_;
302 std::mutex MediaQueryListener::mutex_;
303
JSMatchMediaSync(napi_env env,napi_callback_info info)304 static napi_value JSMatchMediaSync(napi_env env, napi_callback_info info)
305 {
306 /* Get arguments */
307 size_t argc = 1;
308 napi_value argv = nullptr;
309 napi_value thisVar = nullptr;
310 void* data = nullptr;
311 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &argv, &thisVar, &data));
312 NAPI_ASSERT(env, argc == 1, "requires 1 parameter");
313
314 /* Checkout arguments */
315 napi_valuetype type;
316 NAPI_CALL(env, napi_typeof(env, argv, &type));
317 NAPI_ASSERT(env, type == napi_string, "type mismatch");
318 char condition[STR_BUFFER_SIZE] = { 0 };
319 size_t len = 0;
320 napi_get_value_string_utf8(env, argv, condition, STR_BUFFER_SIZE, &len);
321 NAPI_ASSERT(env, len < STR_BUFFER_SIZE, "condition string too long");
322
323 /* construct object for query */
324 std::string conditionStr(condition, len);
325 auto mediaFeature = MediaQueryInfo::GetMediaQueryJsonInfo();
326 MediaQueryer queryer;
327 bool matchResult = queryer.MatchCondition(conditionStr, mediaFeature);
328 MediaQueryListener* listener = new MediaQueryListener(matchResult, conditionStr);
329 napi_value result = nullptr;
330 listener->NapiSerializer(env, result);
331 return result;
332 }
333
Export(napi_env env,napi_value exports)334 static napi_value Export(napi_env env, napi_value exports)
335 {
336 napi_property_descriptor properties[] = { DECLARE_NAPI_FUNCTION("matchMediaSync", JSMatchMediaSync) };
337
338 NAPI_CALL(env, napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties));
339 return exports;
340 }
341
342 static napi_module media_query_module = {
343 .nm_version = 1,
344 .nm_flags = 0,
345 .nm_filename = nullptr,
346 .nm_register_func = Export,
347 .nm_modname = "mediaquery", // relative to the dynamic library's name
348 .nm_priv = ((void*)0),
349 .reserved = { 0 },
350 };
351
Register()352 extern "C" __attribute__((constructor)) void Register()
353 {
354 napi_module_register(&media_query_module);
355 }
356
357 } // namespace OHOS::Ace::Napi
358