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