• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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