• 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         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