* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "js_selection_ability.h"
#include "event_checker.h"
#include "selection_js_utils.h"
#include "napi/native_node_api.h"
#include "selection_log.h"
#include "util.h"
namespace OHOS {
namespace SelectionFwk {
constexpr size_t ARGC_ONE = 1;
constexpr size_t ARGC_TWO = 2;
const std::string JsSelectionAbility::KDS_CLASS_NAME = "SelectionAbility";
thread_local napi_ref JsSelectionAbility::KDSRef_ = nullptr;
std::mutex JsSelectionAbility::selectionMutex_;
std::shared_ptr<JsSelectionAbility> JsSelectionAbility::selectionDelegate_{ nullptr };
napi_value JsSelectionAbility::GetSelectionAbility(napi_env env, napi_callback_info info)
{
SELECTION_HILOGI("GetSelectionAbility");
return nullptr;
}
std::shared_ptr<JsSelectionAbility> JsSelectionAbility::GetJsSelectionAbility()
{
if (selectionDelegate_ == nullptr) {
std::lock_guard<std::mutex> lock(selectionMutex_);
if (selectionDelegate_ == nullptr) {
auto delegate = std::make_shared<JsSelectionAbility>();
if (delegate == nullptr) {
SELECTION_HILOGE("keyboard delegate is nullptr!");
return nullptr;
}
selectionDelegate_ = delegate;
}
}
return selectionDelegate_;
}
napi_value JsSelectionAbility::JsConstructor(napi_env env, napi_callback_info cbinfo)
{
napi_value thisVar = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, nullptr));
auto delegate = GetJsSelectionAbility();
if (delegate == nullptr) {
SELECTION_HILOGE("failed to get delegate!");
napi_value result = nullptr;
napi_get_null(env, &result);
return result;
}
napi_status status = napi_wrap(
env, thisVar, delegate.get(), [](napi_env env, void *nativeObject, void *hint) {}, nullptr, nullptr);
if (status != napi_ok) {
SELECTION_HILOGE("failed to wrap: %{public}d!", status);
return nullptr;
}
return thisVar;
};
napi_value JsSelectionAbility::Subscribe(napi_env env, napi_callback_info info)
{
size_t argc = ARGC_TWO;
napi_value argv[ARGC_TWO] = { nullptr };
napi_value thisVar = nullptr;
void *data = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, &data));
std::string type;
if (argc < 2 || !JsUtil::GetValue(env, argv[0], type) ||
!EventChecker::IsValidEventType(EventSubscribeModule::SELECTION_METHOD_ABILITY, type) ||
JsUtil::GetType(env, argv[1]) != napi_function) {
SELECTION_HILOGE("subscribe failed, type: %{public}s!", type.c_str());
return nullptr;
}
SELECTION_HILOGE("subscribe type: %{public}s.", type.c_str());
auto selectionAbility = reinterpret_cast<JsSelectionAbility *>(JsUtils::GetNativeSelf(env, info));
if (selectionAbility == nullptr) {
return nullptr;
}
std::shared_ptr<JSCallbackObject> callback =
std::make_shared<JSCallbackObject>(env, argv[1], std::this_thread::get_id(),
AppExecFwk::EventHandler::Current());
selectionAbility->RegisterListener(argv[ARGC_ONE], type, callback);
napi_value result = nullptr;
napi_get_null(env, &result);
return result;
}
napi_value JsSelectionAbility::UnSubscribe(napi_env env, napi_callback_info info)
{
size_t argc = ARGC_TWO;
napi_value argv[ARGC_TWO] = { nullptr };
napi_value thisVar = nullptr;
void *data = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, &data));
std::string type;
if (argc < 1 || !JsUtil::GetValue(env, argv[0], type) ||
!EventChecker::IsValidEventType(EventSubscribeModule::SELECTION_METHOD_ABILITY, type)) {
SELECTION_HILOGE("unsubscribe failed, type: %{public}s!", type.c_str());
return nullptr;
}
auto paramType = JsUtil::GetType(env, argv[1]);
if (paramType != napi_function && paramType != napi_null && paramType != napi_undefined) {
return nullptr;
}
argv[1] = paramType == napi_function ? argv[1] : nullptr;
SELECTION_HILOGD("unsubscribe type: %{public}s.", type.c_str());
auto delegate = reinterpret_cast<JsSelectionAbility *>(JsUtils::GetNativeSelf(env, info));
if (delegate == nullptr) {
return nullptr;
}
delegate->UnRegisterListener(argv[ARGC_ONE], type);
napi_value result = nullptr;
napi_get_null(env, &result);
return result;
}
void JsSelectionAbility::RegisterListener(napi_value callback, std::string type,
std::shared_ptr<JSCallbackObject> callbackObj)
{
SELECTION_HILOGD("RegisterListener %{public}s", type.c_str());
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (jsCbMap_.empty() || jsCbMap_.find(type) == jsCbMap_.end()) {
SELECTION_HILOGD("methodName %{public}s is not registered!", type.c_str());
}
auto callbacks = jsCbMap_[type];
bool ret = std::any_of(callbacks.begin(), callbacks.end(), [&callback](std::shared_ptr<JSCallbackObject> cb) {
return JsUtils::Equals(cb->env_, callback, cb->callback_, cb->threadId_);
});
if (ret) {
SELECTION_HILOGD("JsSelectionAbility callback already registered!");
return;
}
SELECTION_HILOGI("add %{public}s callbackObj into jsCbMap_.", type.c_str());
jsCbMap_[type].push_back(std::move(callbackObj));
}
void JsSelectionAbility::UnRegisterListener(napi_value callback, std::string type)
{
SELECTION_HILOGI("unregister listener: %{public}s.", type.c_str());
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (jsCbMap_.empty() || jsCbMap_.find(type) == jsCbMap_.end()) {
SELECTION_HILOGE("methodName %{public}s is not unregistered!", type.c_str());
return;
}
if (callback == nullptr) {
jsCbMap_.erase(type);
SELECTION_HILOGE("callback is nullptr!");
return;
}
for (auto item = jsCbMap_[type].begin(); item != jsCbMap_[type].end(); item++) {
if ((callback != nullptr) &&
(JsUtils::Equals((*item)->env_, callback, (*item)->callback_, (*item)->threadId_))) {
jsCbMap_[type].erase(item);
break;
}
}
if (jsCbMap_[type].empty()) {
jsCbMap_.erase(type);
}
}
napi_value JsSelectionAbility::Init(napi_env env, napi_value exports)
{
SELECTION_HILOGI("napi init");
napi_property_descriptor properties[] = {
DECLARE_NAPI_FUNCTION("getSelectionAbility", GetSelectionAbility),
DECLARE_NAPI_FUNCTION("on", Subscribe),
DECLARE_NAPI_FUNCTION("off", UnSubscribe),
};
napi_value cons = nullptr;
NAPI_CALL(env, napi_define_class(env, KDS_CLASS_NAME.c_str(), KDS_CLASS_NAME.size(), JsConstructor, nullptr,
sizeof(properties) / sizeof(napi_property_descriptor), properties, &cons));
NAPI_CALL(env, napi_create_reference(env, cons, 1, &KDSRef_));
NAPI_CALL(env, napi_set_named_property(env, exports, KDS_CLASS_NAME.c_str(), cons));
return exports;
}
}
}