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