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 std::string mediaInfo;
117 for (auto& listener : copyListeners) {
118 auto scopeId = listener->GetInstanceId();
119 if (scopeId == DEFAULT_INSTANCE_ID) {
120 TAG_LOGE(AceLogTag::ACE_MEDIA_QUERY, "%{public}s:Invalid instance", listener->media_.c_str());
121 }
122 OHOS::Ace::ContainerScope scope(scopeId);
123 auto json = MediaQueryInfo::GetMediaQueryJsonInfo();
124 listener->matches_ = queryer.MatchCondition(listener->media_, json);
125 std::set<napi_ref> delayDeleteCallbacks;
126 std::vector<napi_ref> copyCallbacks;
127 {
128 std::lock_guard<std::mutex> lock(mutex_);
129 auto& currentCallbacks = listener->cbList_;
130 copyCallbacks.insert(copyCallbacks.end(), currentCallbacks.begin(), currentCallbacks.end());
131 }
132
133 for (const auto& cbRef : copyCallbacks) {
134 if (delayDeleteCallbacks_->find(cbRef) != delayDeleteCallbacks_->end()) {
135 continue;
136 }
137 napi_handle_scope scope = nullptr;
138 napi_open_handle_scope(listener->env_, &scope);
139 if (scope == nullptr) {
140 return;
141 }
142
143 napi_value cb = nullptr;
144 napi_get_reference_value(listener->env_, cbRef, &cb);
145
146 napi_value resultArg = nullptr;
147 listener->MediaQueryResult::NapiSerializer(listener->env_, resultArg);
148
149 mediaInfo += listener->media_ + ":" + (listener->matches_ ? "1" : "0") + " ";
150 napi_value result = nullptr;
151 napi_status status = napi_call_function(listener->env_, nullptr, cb, 1, &resultArg, &result);
152 if (status != napi_ok) {
153 TAG_LOGI(AceLogTag::ACE_MEDIA_QUERY, "call faild:%{public}s status:%{public}d",
154 listener->media_.c_str(), status);
155 }
156 napi_close_handle_scope(listener->env_, scope);
157 }
158 }
159 TAG_LOGI(AceLogTag::ACE_MEDIA_QUERY, "trigger: %{public}s", mediaInfo.c_str());
160 }
161
On(napi_env env,napi_callback_info info)162 static napi_value On(napi_env env, napi_callback_info info)
163 {
164 napi_handle_scope scope = nullptr;
165 napi_open_handle_scope(env, &scope);
166 if (scope == nullptr) {
167 return nullptr;
168 }
169 napi_value thisVar = nullptr;
170 napi_value cb = nullptr;
171 size_t argc = ParseArgs(env, info, thisVar, cb);
172 if (!(argc == TWO_ARGS && thisVar != nullptr && cb != nullptr)) {
173 napi_close_handle_scope(env, scope);
174 return nullptr;
175 }
176 MediaQueryListener* listener = GetListener(env, thisVar);
177 if (!listener) {
178 napi_close_handle_scope(env, scope);
179 return nullptr;
180 }
181 auto jsEngine = listener->GetJsEngine();
182 if (!jsEngine) {
183 return nullptr;
184 }
185 jsEngine->RegisterMediaUpdateCallback(NapiCallback);
186 auto iter = listener->FindCbList(cb);
187 if (iter != listener->cbList_.end()) {
188 napi_close_handle_scope(env, scope);
189 return nullptr;
190 }
191 napi_ref ref = nullptr;
192 napi_create_reference(env, cb, 1, &ref);
193 listener->cbList_.emplace_back(ref);
194 TAG_LOGI(AceLogTag::ACE_MEDIA_QUERY, "on:%{public}s num=%{public}d", listener->media_.c_str(),
195 static_cast<int>(listener->cbList_.size()));
196 napi_close_handle_scope(env, scope);
197
198 #if defined(PREVIEW)
199 NapiCallback(AceType::RawPtr(jsEngine));
200 #endif
201
202 return nullptr;
203 }
204
Off(napi_env env,napi_callback_info info)205 static napi_value Off(napi_env env, napi_callback_info info)
206 {
207 napi_value thisVar = nullptr;
208 napi_value cb = nullptr;
209 size_t argc = ParseArgs(env, info, thisVar, cb);
210 MediaQueryListener* listener = GetListener(env, thisVar);
211 if (!listener) {
212 return nullptr;
213 }
214 if (argc == 1) {
215 if (delayDeleteCallbacks_) {
216 delayDeleteEnv_ = env;
217 for (auto& item : listener->cbList_) {
218 (*delayDeleteCallbacks_).emplace(item);
219 }
220 } else {
221 for (auto& item : listener->cbList_) {
222 napi_delete_reference(listener->env_, item);
223 }
224 }
225 listener->cbList_.clear();
226 } else {
227 NAPI_ASSERT(env, (argc == 2 && listener != nullptr && cb != nullptr), "Invalid arguments");
228 auto iter = listener->FindCbList(cb);
229 if (iter != listener->cbList_.end()) {
230 if (delayDeleteCallbacks_) {
231 delayDeleteEnv_ = env;
232 (*delayDeleteCallbacks_).emplace(*iter);
233 } else {
234 napi_delete_reference(listener->env_, *iter);
235 }
236 listener->cbList_.erase(iter);
237 }
238 }
239 TAG_LOGI(AceLogTag::ACE_MEDIA_QUERY, "off:%{public}s num=%{public}d", listener->media_.c_str(),
240 static_cast<int>(listener->cbList_.size()));
241 return nullptr;
242 }
243
FindCbList(napi_value cb)244 std::list<napi_ref>::iterator FindCbList(napi_value cb)
245 {
246 return std::find_if(cbList_.begin(), cbList_.end(), [env = env_, cb](const napi_ref& item) -> bool {
247 bool result = false;
248 napi_value refItem;
249 napi_get_reference_value(env, item, &refItem);
250 napi_strict_equals(env, refItem, cb, &result);
251 return result;
252 });
253 }
254
NapiSerializer(napi_env & env,napi_value & result)255 void NapiSerializer(napi_env& env, napi_value& result) override
256 {
257 MediaQueryResult::NapiSerializer(env, result);
258
259 napi_wrap(
260 env, result, this,
261 [](napi_env env, void* data, void* hint) {
262 MediaQueryListener* listener = static_cast<MediaQueryListener*>(data);
263 if (delayDeleteListenerSets_) {
264 delayDeleteListenerSets_->emplace(listener);
265 } else {
266 delete listener;
267 }
268 },
269 nullptr, nullptr);
270
271 /* insert callback functions */
272 const char* funName = "on";
273 napi_value funcValue = nullptr;
274 napi_create_function(env, funName, NAPI_AUTO_LENGTH, On, nullptr, &funcValue);
275 napi_set_named_property(env, result, funName, funcValue);
276
277 funName = "off";
278 napi_create_function(env, funName, NAPI_AUTO_LENGTH, Off, nullptr, &funcValue);
279 napi_set_named_property(env, result, funName, funcValue);
280 }
281
SetInstanceId(int32_t instanceId)282 void SetInstanceId(int32_t instanceId)
283 {
284 instanceId_ = instanceId;
285 }
286
GetInstanceId()287 int32_t GetInstanceId()
288 {
289 return instanceId_;
290 }
291
292 private:
CleanListenerSet()293 void CleanListenerSet()
294 {
295 auto iter = listenerSets_.begin();
296 while (iter != listenerSets_.end()) {
297 iter->second.erase(this);
298 if (iter->second.empty()) {
299 auto jsEngineWeak = iter->first.Upgrade();
300 if (jsEngineWeak) {
301 jsEngineWeak->UnregisterMediaUpdateCallback();
302 }
303 iter = listenerSets_.erase(iter);
304 } else {
305 iter++;
306 }
307 }
308 }
309
Initialize(napi_env env,napi_value thisVar)310 void Initialize(napi_env env, napi_value thisVar)
311 {
312 napi_handle_scope scope = nullptr;
313 napi_open_handle_scope(env, &scope);
314 if (scope == nullptr) {
315 return;
316 }
317 if (env_ == nullptr) {
318 env_ = env;
319 }
320 napi_close_handle_scope(env, scope);
321 auto jsEngine = GetJsEngine();
322 if (!jsEngine) {
323 return;
324 }
325 {
326 std::lock_guard<std::mutex> lock(mutex_);
327 listenerSets_[jsEngine].emplace(this);
328 }
329 }
330
GetListener(napi_env env,napi_value thisVar)331 static MediaQueryListener* GetListener(napi_env env, napi_value thisVar)
332 {
333 MediaQueryListener* listener = nullptr;
334 napi_unwrap(env, thisVar, (void**)&listener);
335 CHECK_NULL_RETURN(listener, nullptr);
336 listener->Initialize(env, thisVar);
337 return listener;
338 }
339
GetJsEngine()340 RefPtr<Framework::JsEngine> GetJsEngine()
341 {
342 if (GetInstanceId() == DEFAULT_INSTANCE_ID) {
343 TAG_LOGW(AceLogTag::ACE_MEDIA_QUERY, "matchMediaSync executes in non-UI context");
344 return EngineHelper::GetCurrentEngineSafely();
345 } else {
346 return EngineHelper::GetEngine(GetInstanceId());
347 }
348 }
349
ParseArgs(napi_env & env,napi_callback_info & info,napi_value & thisVar,napi_value & cb)350 static size_t ParseArgs(napi_env& env, napi_callback_info& info, napi_value& thisVar, napi_value& cb)
351 {
352 const size_t argNum = 2;
353 size_t argc = argNum;
354 napi_value argv[argNum] = { 0 };
355 void* data = nullptr;
356 napi_get_cb_info(env, info, &argc, argv, &thisVar, &data);
357 NAPI_ASSERT_BASE(env, argc > 0, "too few parameter", 0);
358
359 napi_valuetype napiType;
360 NAPI_CALL_BASE(env, napi_typeof(env, argv[0], &napiType), 0);
361 NAPI_ASSERT_BASE(env, napiType == napi_string, "parameter 1 should be string", 0);
362 char type[STR_BUFFER_SIZE] = { 0 };
363 size_t len = 0;
364 napi_get_value_string_utf8(env, argv[0], type, STR_BUFFER_SIZE, &len);
365 NAPI_ASSERT_BASE(env, len < STR_BUFFER_SIZE, "condition string too long", 0);
366 NAPI_ASSERT_BASE(env, strcmp("change", type) == 0, "type mismatch('change')", 0);
367
368 if (argc <= 1) {
369 return argc;
370 }
371
372 NAPI_CALL_BASE(env, napi_typeof(env, argv[1], &napiType), 0);
373 NAPI_ASSERT_BASE(env, napiType == napi_function, "type mismatch for parameter 2", 0);
374 cb = argv[1];
375 return argc;
376 }
377
378 napi_env env_ = nullptr;
379 std::list<napi_ref> cbList_;
380 int32_t instanceId_ = DEFAULT_INSTANCE_ID;
381 static std::set<std::unique_ptr<MediaQueryListener>>* delayDeleteListenerSets_;
382 static napi_env delayDeleteEnv_;
383 static std::set<napi_ref>* delayDeleteCallbacks_;
384 static std::map<WeakPtr<JsEngine>, std::set<MediaQueryListener*>> listenerSets_;
385 static std::mutex mutex_;
386 };
387 std::set<std::unique_ptr<MediaQueryListener>>* MediaQueryListener::delayDeleteListenerSets_;
388 napi_env MediaQueryListener::delayDeleteEnv_ = nullptr;
389 std::set<napi_ref>* MediaQueryListener::delayDeleteCallbacks_;
390 std::map<WeakPtr<JsEngine>, std::set<MediaQueryListener*>> MediaQueryListener::listenerSets_;
391 std::mutex MediaQueryListener::mutex_;
392
JSMatchMediaSync(napi_env env,napi_callback_info info)393 static napi_value JSMatchMediaSync(napi_env env, napi_callback_info info)
394 {
395 /* Get arguments */
396 size_t argc = 1;
397 napi_value argv = nullptr;
398 napi_value thisVar = nullptr;
399 void* data = nullptr;
400 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &argv, &thisVar, &data));
401 NAPI_ASSERT(env, argc == 1, "requires 1 parameter");
402
403 /* Checkout arguments */
404 napi_valuetype type;
405 NAPI_CALL(env, napi_typeof(env, argv, &type));
406 NAPI_ASSERT(env, type == napi_string, "type mismatch");
407 char condition[STR_BUFFER_SIZE] = { 0 };
408 size_t len = 0;
409 napi_get_value_string_utf8(env, argv, condition, STR_BUFFER_SIZE, &len);
410 NAPI_ASSERT(env, len < STR_BUFFER_SIZE, "condition string too long");
411
412 /* construct object for query */
413 std::string conditionStr(condition, len);
414 auto mediaFeature = MediaQueryInfo::GetMediaQueryJsonInfo();
415 MediaQueryer queryer;
416 bool matchResult = queryer.MatchCondition(conditionStr, mediaFeature);
417 MediaQueryListener* listener = new MediaQueryListener(matchResult, conditionStr);
418 napi_value result = nullptr;
419 listener->NapiSerializer(env, result);
420 listener->SetInstanceId(Container::CurrentIdSafely());
421 return result;
422 }
423
Export(napi_env env,napi_value exports)424 static napi_value Export(napi_env env, napi_value exports)
425 {
426 napi_property_descriptor properties[] = { DECLARE_NAPI_FUNCTION("matchMediaSync", JSMatchMediaSync) };
427
428 NAPI_CALL(env, napi_define_properties(env, exports, sizeof(properties) / sizeof(properties[0]), properties));
429 return exports;
430 }
431
432 static napi_module media_query_module = {
433 .nm_version = 1,
434 .nm_flags = 0,
435 .nm_filename = nullptr,
436 .nm_register_func = Export,
437 .nm_modname = "mediaquery", // relative to the dynamic library's name
438 .nm_priv = ((void*)0),
439 .reserved = { 0 },
440 };
441
RegisterMediaQuery()442 extern "C" __attribute__((constructor)) void RegisterMediaQuery()
443 {
444 napi_module_register(&media_query_module);
445 }
446
447 } // namespace OHOS::Ace::Napi
448