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