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