* 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_panel.h"
#include "napi/native_common.h"
#include "napi/native_node_api.h"
#include "selection_log.h"
#include "panel_listener_impl.h"
#include "selection_js_utils.h"
#include "selectionmethod_trace.h"
#include "selection_ability.h"
#include "event_checker.h"
#include "selection_api_event_reporter.h"
namespace OHOS {
namespace SelectionFwk {
using namespace std::chrono;
constexpr int32_t MAX_WAIT_TIME = 10;
const std::string JsPanel::CLASS_NAME = "Panel";
thread_local napi_ref JsPanel::panelConstructorRef_ = nullptr;
std::mutex JsPanel::panelConstructorMutex_;
FFRTBlockQueue<JsEventInfo> JsPanel::jsQueue_{ MAX_WAIT_TIME };
constexpr size_t ARGC_MAX = 6;
napi_value JsPanel::Init(napi_env env)
{
SELECTION_HILOGI("JsPanel start.");
napi_value constructor = nullptr;
std::lock_guard<std::mutex> lock(panelConstructorMutex_);
if (panelConstructorRef_ != nullptr) {
napi_status status = napi_get_reference_value(env, panelConstructorRef_, &constructor);
CHECK_RETURN(status == napi_ok, "failed to get jsPanel constructor.", nullptr);
return constructor;
}
const napi_property_descriptor properties[] = {
DECLARE_NAPI_FUNCTION("setUiContent", SetUiContent),
DECLARE_NAPI_FUNCTION("show", Show),
DECLARE_NAPI_FUNCTION("hide", Hide),
DECLARE_NAPI_FUNCTION("startMoving", StartMoving),
DECLARE_NAPI_FUNCTION("moveTo", MoveTo),
DECLARE_NAPI_FUNCTION("moveToGlobalDisplay", MoveTo),
DECLARE_NAPI_FUNCTION("on", Subscribe),
DECLARE_NAPI_FUNCTION("off", UnSubscribe)
};
NAPI_CALL(env, napi_define_class(env, CLASS_NAME.c_str(), CLASS_NAME.size(), JsNew, nullptr,
sizeof(properties) / sizeof(napi_property_descriptor), properties, &constructor));
CHECK_RETURN(constructor != nullptr, "failed to define class!", nullptr);
NAPI_CALL(env, napi_create_reference(env, constructor, 1, &panelConstructorRef_));
return constructor;
}
napi_value JsPanel::JsNew(napi_env env, napi_callback_info info)
{
SELECTION_HILOGD("create panel instance start.");
std::shared_ptr<PanelListenerImpl> panelImpl = PanelListenerImpl::GetInstance();
if (panelImpl != nullptr) {
SELECTION_HILOGD("set eventHandler.");
panelImpl->SetEventHandler(AppExecFwk::EventHandler::Current());
}
JsPanel *panel = new (std::nothrow) JsPanel();
CHECK_RETURN(panel != nullptr, "no memory for JsPanel!", nullptr);
auto finalize = [](napi_env env, void *data, void *hint) {
SELECTION_HILOGD("jsPanel finalize.");
auto *jsPanel = reinterpret_cast<JsPanel *>(data);
CHECK_RETURN_VOID(jsPanel != nullptr, "finalize nullptr!");
jsPanel->GetNative().reset();
if (jsPanel != nullptr) {
delete jsPanel;
}
};
napi_value thisVar = nullptr;
napi_status status = napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
if (status != napi_ok) {
SELECTION_HILOGE("failed to get cb info: %{public}d!", status);
delete panel;
return nullptr;
}
status = napi_wrap(env, thisVar, panel, finalize, nullptr, nullptr);
if (status != napi_ok) {
SELECTION_HILOGE("failed to wrap: %{public}d!", status);
delete panel;
return nullptr;
}
return thisVar;
}
JsPanel::~JsPanel()
{
selectionPanel_ = nullptr;
}
void JsPanel::SetNative(const std::shared_ptr<SelectionPanel> &panel)
{
selectionPanel_ = panel;
}
std::shared_ptr<SelectionPanel> JsPanel::GetNative()
{
return selectionPanel_;
}
napi_value JsPanel::SetUiContent(napi_env env, napi_callback_info info)
{
SELECTION_HILOGI("JsPanel SetUiContent start.");
auto ctxt = std::make_shared<PanelContentContext>(env, info);
auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
napi_status status = napi_generic_failure;
PARAM_CHECK_RETURN(env, argc >= 1, "at least one parameter is required!", TYPE_NONE, status);
PARAM_CHECK_RETURN(env, JsUtils::GetValue(env, argv[0], ctxt->path) == napi_ok,
"js param path covert failed, must be string!", TYPE_NONE, status);
ctxt->info = { std::chrono::system_clock::now(), JsEvent::SET_UI_CONTENT };
jsQueue_.Push(ctxt->info);
return napi_ok;
};
auto exec = [ctxt](AsyncCall::Context *ctx) { ctxt->SetState(napi_ok); };
auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
jsQueue_.Wait(ctxt->info);
if (ctxt->selectionPanel == nullptr) {
SELECTION_HILOGE("selectionPanel is nullptr!");
jsQueue_.Pop();
ctxt->SetErrorCode(SFErrorCode::EXCEPTION_PANEL_DESTROYED);
return napi_generic_failure;
}
auto code = ctxt->selectionPanel->SetUiContent(ctxt->path, env);
jsQueue_.Pop();
if (code != ErrorCode::NO_ERROR) {
ctxt->SetErrorCode(code);
return napi_generic_failure;
}
return napi_ok;
};
ctxt->SetAction(std::move(input), std::move(output));
AsyncCall asyncCall(env, info, ctxt, 2);
return asyncCall.Call(env, exec, "setUiContent");
}
napi_value JsPanel::Show(napi_env env, napi_callback_info info)
{
SELECTION_HILOGI("JsPanel Show start.");
SelectionMethodSyncTrace tracer("JsPanel_Show");
auto ctxt = std::make_shared<PanelContentContext>(env, info);
auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
ctxt->info = { std::chrono::system_clock::now(), JsEvent::SHOW };
jsQueue_.Push(ctxt->info);
return napi_ok;
};
auto exec = [ctxt](AsyncCall::Context *ctx) {
jsQueue_.Wait(ctxt->info);
if (ctxt->selectionPanel == nullptr) {
SELECTION_HILOGE("selectionPanel is nullptr!");
jsQueue_.Pop();
ctxt->SetErrorCode(SFErrorCode::EXCEPTION_PANEL_DESTROYED);
return;
}
auto code = SelectionAbility::GetInstance()->ShowPanel(ctxt->selectionPanel);
jsQueue_.Pop();
if (code == ErrorCode::NO_ERROR) {
ctxt->SetState(napi_ok);
return;
}
ctxt->SetErrorCode(code);
};
ctxt->SetAction(std::move(input));
AsyncCall asyncCall(env, info, ctxt, 1);
return asyncCall.Call(env, exec, "show");
}
napi_value JsPanel::Hide(napi_env env, napi_callback_info info)
{
SELECTION_HILOGI("JsPanel Hide start.");
SelectionMethodSyncTrace tracer("JsPanel_Hide");
auto ctxt = std::make_shared<PanelContentContext>(env, info);
auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
ctxt->info = { std::chrono::system_clock::now(), JsEvent::HIDE };
jsQueue_.Push(ctxt->info);
return napi_ok;
};
auto exec = [ctxt](AsyncCall::Context *ctx) {
jsQueue_.Wait(ctxt->info);
if (ctxt->selectionPanel == nullptr) {
SELECTION_HILOGE("selectionPanel is nullptr!");
jsQueue_.Pop();
ctxt->SetErrorCode(SFErrorCode::EXCEPTION_PANEL_DESTROYED);
return;
}
auto code = SelectionAbility::GetInstance()->HidePanel(ctxt->selectionPanel);
jsQueue_.Pop();
if (code == ErrorCode::NO_ERROR) {
ctxt->SetState(napi_ok);
return;
}
ctxt->SetErrorCode(code);
};
ctxt->SetAction(std::move(input));
AsyncCall asyncCall(env, info, ctxt, 1);
return asyncCall.Call(env, exec, "panel.hide");
}
napi_value JsPanel::StartMoving(napi_env env, napi_callback_info info)
{
SELECTION_HILOGI("JsPanel StartMoving start!");
auto ctxt = std::make_shared<PanelContentContext>(env, info);
auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
ctxt->info = { std::chrono::system_clock::now(), JsEvent::START_MOVING };
jsQueue_.Push(ctxt->info);
return napi_ok;
};
auto exec = [ctxt](AsyncCall::Context *ctx) {
jsQueue_.Wait(ctxt->info);
if (ctxt->selectionPanel == nullptr) {
SELECTION_HILOGE("selectionPanel is nullptr!");
jsQueue_.Pop();
ctxt->SetErrorCode(SFErrorCode::EXCEPTION_PANEL_DESTROYED);
return;
}
auto code = ctxt->selectionPanel->StartMoving();
jsQueue_.Pop();
if (code != ErrorCode::NO_ERROR) {
ctxt->SetErrorCode(code);
return;
}
ctxt->SetState(napi_ok);
};
ctxt->SetAction(std::move(input));
AsyncCall asyncCall(env, info, ctxt, 1);
return asyncCall.Call(env, exec, "startMoving");
}
napi_value JsPanel::MoveTo(napi_env env, napi_callback_info info)
{
SELECTION_HILOGI("JsPanel MoveTo start!");
auto ctxt = std::make_shared<PanelContentContext>(env, info);
auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
napi_status status = napi_generic_failure;
PARAM_CHECK_RETURN(env, argc > 1, "at least two parameters is required ", TYPE_NONE, status);
PARAM_CHECK_RETURN(env, JsUtils::GetValue(env, argv[0], ctxt->x) == napi_ok, "x type must be number",
TYPE_NONE, status);
PARAM_CHECK_RETURN(env, JsUtils::GetValue(env, argv[1], ctxt->y) == napi_ok, "y type must be number",
TYPE_NONE, status);
ctxt->info = { std::chrono::system_clock::now(), JsEvent::MOVE_TO };
jsQueue_.Push(ctxt->info);
return napi_ok;
};
auto exec = [ctxt](AsyncCall::Context *ctx) {
int64_t start = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
jsQueue_.Wait(ctxt->info);
PrintEditorQueueInfoIfTimeout(start, ctxt->info);
if (ctxt->selectionPanel == nullptr) {
SELECTION_HILOGE("selectionPanel is nullptr!");
jsQueue_.Pop();
ctxt->SetErrorCode(SFErrorCode::EXCEPTION_PANEL_DESTROYED);
return;
}
auto code = ctxt->selectionPanel->MoveTo(ctxt->x, ctxt->y);
jsQueue_.Pop();
if (code != ErrorCode::NO_ERROR) {
ctxt->SetErrorCode(code);
return;
}
ctxt->SetState(napi_ok);
};
ctxt->SetAction(std::move(input));
AsyncCall asyncCall(env, info, ctxt, 3);
return asyncCall.Call(env, exec, "moveTo");
}
void JsPanel::PrintEditorQueueInfoIfTimeout(int64_t start, const JsEventInfo ¤tInfo)
{
int64_t end = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
if (end - start >= MAX_WAIT_TIME) {
JsEventInfo frontInfo;
auto ret = jsQueue_.GetFront(frontInfo);
int64_t frontTime = duration_cast<microseconds>(frontInfo.timestamp.time_since_epoch()).count();
int64_t currentTime = duration_cast<microseconds>(currentInfo.timestamp.time_since_epoch()).count();
SELECTION_HILOGI("ret:%{public}d,front[%{public}" PRId64 ",%{public}d],current[%{public}" PRId64
",%{public}d]", ret, frontTime, static_cast<int32_t>(frontInfo.event), currentTime,
static_cast<int32_t>(currentInfo.event));
}
}
napi_value JsPanel::Subscribe(napi_env env, napi_callback_info info)
{
SELECTION_HILOGD("JsPanel start.");
size_t argc = ARGC_MAX;
napi_value argv[ARGC_MAX] = {nullptr};
napi_value thisVar = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
std::string type;
if (argc < 2 || !JsUtil::GetValue(env, argv[0], type) ||
!EventChecker::IsValidEventType(EventSubscribeModule::PANEL, type) ||
JsUtil::GetType(env, argv[1]) != napi_function) {
SELECTION_HILOGE("subscribe failed, type: %{public}s!", type.c_str());
JsUtils::ThrowException(env, SFErrorCode::EXCEPTION_PARAMCHECK, "", TYPE_NONE);
return nullptr;
}
SELECTION_HILOGD("subscribe type: %{public}s.", type.c_str());
std::shared_ptr<PanelListenerImpl> observer = PanelListenerImpl::GetInstance();
auto selectionPanel = UnwrapPanel(env, thisVar);
if (selectionPanel == nullptr) {
SELECTION_HILOGE("selectionPanel is nullptr!");
return nullptr;
}
std::shared_ptr<JSCallbackObject> cbObject = std::make_shared<JSCallbackObject>(
env, argv[1], std::this_thread::get_id(), AppExecFwk::EventHandler::Current());
observer->Subscribe(selectionPanel->GetWindowId(), type, cbObject);
bool ret = selectionPanel->SetPanelStatusListener(observer, type);
if (!ret) {
SELECTION_HILOGE("failed to subscribe %{public}s!", type.c_str());
observer->RemoveInfo(type, selectionPanel->GetWindowId());
}
napi_value result = nullptr;
napi_get_undefined(env, &result);
return result;
}
napi_value JsPanel::UnSubscribe(napi_env env, napi_callback_info info)
{
size_t argc = ARGC_MAX;
napi_value argv[ARGC_MAX] = { nullptr };
napi_value thisVar = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
std::string type;
PARAM_CHECK_RETURN(env, argc >= 1, "at least one parameter is required!", TYPE_NONE, nullptr);
PARAM_CHECK_RETURN(env, JsUtil::GetValue(env, argv[0], type), "type must be string!", TYPE_NONE, nullptr);
PARAM_CHECK_RETURN(env, EventChecker::IsValidEventType(EventSubscribeModule::PANEL, type),
"type should be show/hide/sizeChange!", TYPE_NONE, nullptr);
auto paramType = JsUtil::GetType(env, argv[1]);
PARAM_CHECK_RETURN(env, (paramType == napi_function || paramType == napi_null || paramType == napi_undefined),
"callback should be function or null or undefined!", TYPE_NONE, nullptr);
argv[1] = paramType == napi_function ? argv[1] : nullptr;
SELECTION_HILOGD("unsubscribe type: %{public}s.", type.c_str());
std::shared_ptr<PanelListenerImpl> observer = PanelListenerImpl::GetInstance();
auto selectionPanel = UnwrapPanel(env, thisVar);
if (selectionPanel == nullptr) {
SELECTION_HILOGE("selectionPanel is nullptr!");
return nullptr;
}
if (paramType == napi_function) {
std::shared_ptr<JSCallbackObject> cbObject = std::make_shared<JSCallbackObject>(
env, argv[1], std::this_thread::get_id(), AppExecFwk::EventHandler::Current());
observer->RemoveInfo(type, selectionPanel->GetWindowId(), cbObject);
} else {
observer->RemoveInfo(type, selectionPanel->GetWindowId());
selectionPanel->ClearPanelListener(type);
}
napi_value result = nullptr;
napi_get_null(env, &result);
return result;
}
std::shared_ptr<SelectionPanel> JsPanel::UnwrapPanel(napi_env env, napi_value thisVar)
{
void *native = nullptr;
napi_status status = napi_unwrap(env, thisVar, &native);
CHECK_RETURN((status == napi_ok && native != nullptr), "failed to unwrap!", nullptr);
auto jsPanel = reinterpret_cast<JsPanel *>(native);
if (jsPanel == nullptr) {
return nullptr;
}
auto selectionPanel = jsPanel->GetNative();
CHECK_RETURN(selectionPanel != nullptr, "SelectionPanel is nullptr", nullptr);
return selectionPanel;
}
}
}