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