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