• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2023 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 "js_get_input_method_controller.h"
16 
17 #include <set>
18 
19 #include "event_checker.h"
20 #include "inputmethod_trace.h"
21 #include "input_method_controller.h"
22 #include "input_method_utils.h"
23 #include "js_callback_handler.h"
24 #include "js_get_input_method_textchange_listener.h"
25 #include "js_util.h"
26 #include "napi/native_api.h"
27 #include "napi/native_node_api.h"
28 #include "string_ex.h"
29 
30 namespace OHOS {
31 namespace MiscServices {
32 constexpr size_t ARGC_ZERO = 0;
33 constexpr size_t ARGC_ONE = 1;
34 constexpr size_t ARGC_TWO = 2;
35 constexpr int32_t MAX_WAIT_TIME_MESSAGE_HANDLER = 2000;
36 const std::set<std::string> EVENT_TYPE{
37     "selectByRange",
38     "selectByMovement",
39 };
40 const std::set<std::string> JsGetInputMethodController::TEXT_EVENT_TYPE{
41     "insertText",
42     "deleteLeft",
43     "deleteRight",
44     "sendKeyboardStatus",
45     "sendFunctionKey",
46     "moveCursor",
47     "handleExtendAction",
48     "getLeftTextOfCursor",
49     "getRightTextOfCursor",
50     "getTextIndexAtCursor",
51 };
52 thread_local napi_ref JsGetInputMethodController::IMCRef_ = nullptr;
53 const std::string JsGetInputMethodController::IMC_CLASS_NAME = "InputMethodController";
54 std::mutex JsGetInputMethodController::controllerMutex_;
55 std::shared_ptr<JsGetInputMethodController> JsGetInputMethodController::controller_{ nullptr };
56 std::mutex JsGetInputMethodController::eventHandlerMutex_;
57 std::shared_ptr<AppExecFwk::EventHandler> JsGetInputMethodController::handler_{ nullptr };
58 BlockQueue<MessageHandlerInfo> JsGetInputMethodController::messageHandlerQueue_{ MAX_WAIT_TIME_MESSAGE_HANDLER };
Init(napi_env env,napi_value info)59 napi_value JsGetInputMethodController::Init(napi_env env, napi_value info)
60 {
61     napi_property_descriptor descriptor[] = {
62         DECLARE_NAPI_FUNCTION("getInputMethodController", GetInputMethodController),
63         DECLARE_NAPI_FUNCTION("getController", GetController),
64         DECLARE_NAPI_STATIC_PROPERTY("KeyboardStatus", GetJsKeyboardStatusProperty(env)),
65         DECLARE_NAPI_STATIC_PROPERTY("EnterKeyType", GetJsEnterKeyTypeProperty(env)),
66         DECLARE_NAPI_STATIC_PROPERTY("TextInputType", GetJsTextInputTypeProperty(env)),
67         DECLARE_NAPI_STATIC_PROPERTY("Direction", GetJsDirectionProperty(env)),
68         DECLARE_NAPI_STATIC_PROPERTY("ExtendAction", GetJsExtendActionProperty(env)),
69         DECLARE_NAPI_STATIC_PROPERTY("EnabledState", GetJsEnabledStateProperty(env)),
70         DECLARE_NAPI_STATIC_PROPERTY("RequestKeyboardReason", GetJsRequestKeyboardReasonProperty(env))
71     };
72     NAPI_CALL(
73         env, napi_define_properties(env, info, sizeof(descriptor) / sizeof(napi_property_descriptor), descriptor));
74 
75     napi_property_descriptor properties[] = {
76         DECLARE_NAPI_FUNCTION("attach", Attach),
77         DECLARE_NAPI_FUNCTION("detach", Detach),
78         DECLARE_NAPI_FUNCTION("showTextInput", ShowTextInput),
79         DECLARE_NAPI_FUNCTION("hideTextInput", HideTextInput),
80         DECLARE_NAPI_FUNCTION("setCallingWindow", SetCallingWindow),
81         DECLARE_NAPI_FUNCTION("updateCursor", UpdateCursor),
82         DECLARE_NAPI_FUNCTION("changeSelection", ChangeSelection),
83         DECLARE_NAPI_FUNCTION("updateAttribute", UpdateAttribute),
84         DECLARE_NAPI_FUNCTION("stopInput", StopInput),
85         DECLARE_NAPI_FUNCTION("stopInputSession", StopInputSession),
86         DECLARE_NAPI_FUNCTION("hideSoftKeyboard", HideSoftKeyboard),
87         DECLARE_NAPI_FUNCTION("showSoftKeyboard", ShowSoftKeyboard),
88         DECLARE_NAPI_FUNCTION("on", Subscribe),
89         DECLARE_NAPI_FUNCTION("off", UnSubscribe),
90         DECLARE_NAPI_FUNCTION("sendMessage", SendMessage),
91         DECLARE_NAPI_FUNCTION("recvMessage", RecvMessage),
92     };
93     napi_value cons = nullptr;
94     NAPI_CALL(env, napi_define_class(env, IMC_CLASS_NAME.c_str(), IMC_CLASS_NAME.size(), JsConstructor, nullptr,
95                        sizeof(properties) / sizeof(napi_property_descriptor), properties, &cons));
96     NAPI_CALL(env, napi_create_reference(env, cons, 1, &IMCRef_));
97     NAPI_CALL(env, napi_set_named_property(env, info, IMC_CLASS_NAME.c_str(), cons));
98 
99     return info;
100 }
101 
GetJsKeyboardStatusProperty(napi_env env)102 napi_value JsGetInputMethodController::GetJsKeyboardStatusProperty(napi_env env)
103 {
104     napi_value keyboardStatus = nullptr;
105     napi_value statusNone = nullptr;
106     napi_value statusHide = nullptr;
107     napi_value statusShow = nullptr;
108     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(KeyboardStatus::NONE), &statusNone));
109     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(KeyboardStatus::HIDE), &statusHide));
110     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(KeyboardStatus::SHOW), &statusShow));
111     NAPI_CALL(env, napi_create_object(env, &keyboardStatus));
112     NAPI_CALL(env, napi_set_named_property(env, keyboardStatus, "NONE", statusNone));
113     NAPI_CALL(env, napi_set_named_property(env, keyboardStatus, "HIDE", statusHide));
114     NAPI_CALL(env, napi_set_named_property(env, keyboardStatus, "SHOW", statusShow));
115     return keyboardStatus;
116 }
117 
GetJsEnterKeyTypeProperty(napi_env env)118 napi_value JsGetInputMethodController::GetJsEnterKeyTypeProperty(napi_env env)
119 {
120     napi_value enterKeyType = nullptr;
121     napi_value typeUnspecified = nullptr;
122     napi_value typeNone = nullptr;
123     napi_value typeGo = nullptr;
124     napi_value typeSearch = nullptr;
125     napi_value typeSend = nullptr;
126     napi_value typeNext = nullptr;
127     napi_value typeDone = nullptr;
128     napi_value typePrevious = nullptr;
129     napi_value typeNewline = nullptr;
130     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnterKeyType::UNSPECIFIED), &typeUnspecified));
131     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnterKeyType::NONE), &typeNone));
132     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnterKeyType::GO), &typeGo));
133     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnterKeyType::SEARCH), &typeSearch));
134     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnterKeyType::SEND), &typeSend));
135     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnterKeyType::NEXT), &typeNext));
136     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnterKeyType::DONE), &typeDone));
137     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnterKeyType::PREVIOUS), &typePrevious));
138     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnterKeyType::NEW_LINE), &typeNewline));
139     NAPI_CALL(env, napi_create_object(env, &enterKeyType));
140     NAPI_CALL(env, napi_set_named_property(env, enterKeyType, "UNSPECIFIED", typeUnspecified));
141     NAPI_CALL(env, napi_set_named_property(env, enterKeyType, "NONE", typeNone));
142     NAPI_CALL(env, napi_set_named_property(env, enterKeyType, "GO", typeGo));
143     NAPI_CALL(env, napi_set_named_property(env, enterKeyType, "SEARCH", typeSearch));
144     NAPI_CALL(env, napi_set_named_property(env, enterKeyType, "SEND", typeSend));
145     NAPI_CALL(env, napi_set_named_property(env, enterKeyType, "NEXT", typeNext));
146     NAPI_CALL(env, napi_set_named_property(env, enterKeyType, "DONE", typeDone));
147     NAPI_CALL(env, napi_set_named_property(env, enterKeyType, "PREVIOUS", typePrevious));
148     NAPI_CALL(env, napi_set_named_property(env, enterKeyType, "NEWLINE", typeNewline));
149     return enterKeyType;
150 }
151 
GetJsTextInputTypeProperty(napi_env env)152 napi_value JsGetInputMethodController::GetJsTextInputTypeProperty(napi_env env)
153 {
154     napi_value textInputType = nullptr;
155     napi_value typeNone = nullptr;
156     napi_value typeText = nullptr;
157     napi_value typeMultiline = nullptr;
158     napi_value typeNumber = nullptr;
159     napi_value typePhone = nullptr;
160     napi_value typeDatatime = nullptr;
161     napi_value typeEmailAddress = nullptr;
162     napi_value typeUrl = nullptr;
163     napi_value typeVisiblePassword = nullptr;
164     napi_value typeNumberPassword = nullptr;
165     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(TextInputType::NONE), &typeNone));
166     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(TextInputType::TEXT), &typeText));
167     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(TextInputType::MULTILINE), &typeMultiline));
168     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(TextInputType::NUMBER), &typeNumber));
169     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(TextInputType::PHONE), &typePhone));
170     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(TextInputType::DATETIME), &typeDatatime));
171     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(TextInputType::EMAIL_ADDRESS), &typeEmailAddress));
172     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(TextInputType::URL), &typeUrl));
173     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(TextInputType::VISIBLE_PASSWORD), &typeVisiblePassword));
174     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(TextInputType::NUMBER_PASSWORD), &typeNumberPassword));
175     NAPI_CALL(env, napi_create_object(env, &textInputType));
176     NAPI_CALL(env, napi_set_named_property(env, textInputType, "NONE", typeNone));
177     NAPI_CALL(env, napi_set_named_property(env, textInputType, "TEXT", typeText));
178     NAPI_CALL(env, napi_set_named_property(env, textInputType, "MULTILINE", typeMultiline));
179     NAPI_CALL(env, napi_set_named_property(env, textInputType, "NUMBER", typeNumber));
180     NAPI_CALL(env, napi_set_named_property(env, textInputType, "PHONE", typePhone));
181     NAPI_CALL(env, napi_set_named_property(env, textInputType, "DATETIME", typeDatatime));
182     NAPI_CALL(env, napi_set_named_property(env, textInputType, "EMAIL_ADDRESS", typeEmailAddress));
183     NAPI_CALL(env, napi_set_named_property(env, textInputType, "URL", typeUrl));
184     NAPI_CALL(env, napi_set_named_property(env, textInputType, "VISIBLE_PASSWORD", typeVisiblePassword));
185     NAPI_CALL(env, napi_set_named_property(env, textInputType, "NUMBER_PASSWORD", typeNumberPassword));
186     return textInputType;
187 }
188 
GetJsDirectionProperty(napi_env env)189 napi_value JsGetInputMethodController::GetJsDirectionProperty(napi_env env)
190 {
191     napi_value direction = nullptr;
192     napi_value cursorUp = nullptr;
193     napi_value cursorDown = nullptr;
194     napi_value cursorLeft = nullptr;
195     napi_value cursorRight = nullptr;
196     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(Direction::UP), &cursorUp));
197     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(Direction::DOWN), &cursorDown));
198     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(Direction::LEFT), &cursorLeft));
199     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(Direction::RIGHT), &cursorRight));
200     NAPI_CALL(env, napi_create_object(env, &direction));
201     NAPI_CALL(env, napi_set_named_property(env, direction, "CURSOR_UP", cursorUp));
202     NAPI_CALL(env, napi_set_named_property(env, direction, "CURSOR_DOWN", cursorDown));
203     NAPI_CALL(env, napi_set_named_property(env, direction, "CURSOR_LEFT", cursorLeft));
204     NAPI_CALL(env, napi_set_named_property(env, direction, "CURSOR_RIGHT", cursorRight));
205     return direction;
206 }
207 
GetJsExtendActionProperty(napi_env env)208 napi_value JsGetInputMethodController::GetJsExtendActionProperty(napi_env env)
209 {
210     napi_value action = nullptr;
211     napi_value actionSelectAll = nullptr;
212     napi_value actionCut = nullptr;
213     napi_value actionCopy = nullptr;
214     napi_value actionPaste = nullptr;
215     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(ExtendAction::SELECT_ALL), &actionSelectAll));
216     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(ExtendAction::CUT), &actionCut));
217     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(ExtendAction::COPY), &actionCopy));
218     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(ExtendAction::PASTE), &actionPaste));
219     NAPI_CALL(env, napi_create_object(env, &action));
220     NAPI_CALL(env, napi_set_named_property(env, action, "SELECT_ALL", actionSelectAll));
221     NAPI_CALL(env, napi_set_named_property(env, action, "CUT", actionCut));
222     NAPI_CALL(env, napi_set_named_property(env, action, "COPY", actionCopy));
223     NAPI_CALL(env, napi_set_named_property(env, action, "PASTE", actionPaste));
224     return action;
225 }
226 
GetJsEnabledStateProperty(napi_env env)227 napi_value JsGetInputMethodController::GetJsEnabledStateProperty(napi_env env)
228 {
229     napi_value status = nullptr;
230     napi_value disabled = nullptr;
231     napi_value basicMode = nullptr;
232     napi_value fullExperience = nullptr;
233     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnabledStatus::DISABLED), &disabled));
234     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnabledStatus::BASIC_MODE), &basicMode));
235     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(EnabledStatus::FULL_EXPERIENCE_MODE), &fullExperience));
236     NAPI_CALL(env, napi_create_object(env, &status));
237     NAPI_CALL(env, napi_set_named_property(env, status, "DISABLED", disabled));
238     NAPI_CALL(env, napi_set_named_property(env, status, "BASIC_MODE", basicMode));
239     NAPI_CALL(env, napi_set_named_property(env, status, "FULL_EXPERIENCE_MODE", fullExperience));
240     return status;
241 }
242 
GetJsRequestKeyboardReasonProperty(napi_env env)243 napi_value JsGetInputMethodController::GetJsRequestKeyboardReasonProperty(napi_env env)
244 {
245     napi_value requestKeyboardReason = nullptr;
246     napi_value none = nullptr;
247     napi_value mouse = nullptr;
248     napi_value touch = nullptr;
249     napi_value other = nullptr;
250     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(RequestKeyboardReason::NONE), &none));
251     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(RequestKeyboardReason::MOUSE), &mouse));
252     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(RequestKeyboardReason::TOUCH), &touch));
253     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(RequestKeyboardReason::OTHER), &other));
254     NAPI_CALL(env, napi_create_object(env, &requestKeyboardReason));
255     NAPI_CALL(env, napi_set_named_property(env, requestKeyboardReason, "NONE", none));
256     NAPI_CALL(env, napi_set_named_property(env, requestKeyboardReason, "MOUSE", mouse));
257     NAPI_CALL(env, napi_set_named_property(env, requestKeyboardReason, "TOUCH", touch));
258     NAPI_CALL(env, napi_set_named_property(env, requestKeyboardReason, "OTHER", other));
259     return requestKeyboardReason;
260 }
261 
JsConstructor(napi_env env,napi_callback_info cbinfo)262 napi_value JsGetInputMethodController::JsConstructor(napi_env env, napi_callback_info cbinfo)
263 {
264     {
265         std::lock_guard<std::mutex> lock(eventHandlerMutex_);
266         handler_ = AppExecFwk::EventHandler::Current();
267     }
268     napi_value thisVar = nullptr;
269     NAPI_CALL(env, napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, nullptr));
270 
271     auto controllerObject = GetInstance();
272     if (controllerObject == nullptr) {
273         IMSA_HILOGE("controllerObject is nullptr!");
274         napi_value result = nullptr;
275         napi_get_null(env, &result);
276         return result;
277     }
278     napi_status status = napi_wrap(
279         env, thisVar, controllerObject.get(), [](napi_env env, void *data, void *hint) {}, nullptr, nullptr);
280     if (status != napi_ok) {
281         IMSA_HILOGE("failed to wrap: %{public}d!", status);
282         return nullptr;
283     }
284 
285     if (controllerObject->loop_ == nullptr) {
286         napi_get_uv_event_loop(env, &controllerObject->loop_);
287     }
288 
289     return thisVar;
290 }
291 
GetInputMethodController(napi_env env,napi_callback_info cbInfo)292 napi_value JsGetInputMethodController::GetInputMethodController(napi_env env, napi_callback_info cbInfo)
293 {
294     return GetIMController(env, cbInfo, false);
295 }
296 
GetController(napi_env env,napi_callback_info cbInfo)297 napi_value JsGetInputMethodController::GetController(napi_env env, napi_callback_info cbInfo)
298 {
299     return GetIMController(env, cbInfo, true);
300 }
301 
GetIMController(napi_env env,napi_callback_info cbInfo,bool needThrowException)302 napi_value JsGetInputMethodController::GetIMController(napi_env env, napi_callback_info cbInfo, bool needThrowException)
303 {
304     napi_value instance = nullptr;
305     napi_value cons = nullptr;
306     if (napi_get_reference_value(env, IMCRef_, &cons) != napi_ok) {
307         IMSA_HILOGE("failed to get reference value!");
308         if (needThrowException) {
309             JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_CONTROLLER, "", TYPE_OBJECT);
310         }
311         return nullptr;
312     }
313 
314     if (napi_new_instance(env, cons, 0, nullptr, &instance) != napi_ok) {
315         IMSA_HILOGE("failed to create new instance!");
316         if (needThrowException) {
317             JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_CONTROLLER, "", TYPE_OBJECT);
318         }
319         return nullptr;
320     }
321     return instance;
322 }
323 
GetInstance()324 std::shared_ptr<JsGetInputMethodController> JsGetInputMethodController::GetInstance()
325 {
326     if (controller_ == nullptr) {
327         std::lock_guard<std::mutex> lock(controllerMutex_);
328         if (controller_ == nullptr) {
329             auto controller = std::make_shared<JsGetInputMethodController>();
330             controller_ = controller;
331             InputMethodController::GetInstance()->SetControllerListener(controller_);
332         }
333     }
334     return controller_;
335 }
336 
RegisterListener(napi_value callback,std::string type,std::shared_ptr<JSCallbackObject> callbackObj)337 void JsGetInputMethodController::RegisterListener(
338     napi_value callback, std::string type, std::shared_ptr<JSCallbackObject> callbackObj)
339 {
340     IMSA_HILOGD("start, type: %{public}s", type.c_str());
341     std::lock_guard<std::recursive_mutex> lock(mutex_);
342     if (jsCbMap_.empty() || jsCbMap_.find(type) == jsCbMap_.end()) {
343         IMSA_HILOGD("methodName: %{public}s is not registered!", type.c_str());
344     }
345 
346     auto callbacks = jsCbMap_[type];
347     bool ret = std::any_of(callbacks.begin(), callbacks.end(), [&callback](std::shared_ptr<JSCallbackObject> cb) {
348         return JsUtils::Equals(cb->env_, callback, cb->callback_, cb->threadId_);
349     });
350     if (ret) {
351         IMSA_HILOGD("callback already registered.");
352         return;
353     }
354 
355     IMSA_HILOGI("add %{public}s callbackObj into jsCbMap_.", type.c_str());
356     jsCbMap_[type].push_back(std::move(callbackObj));
357 }
358 
UnRegisterListener(napi_value callback,std::string type)359 void JsGetInputMethodController::UnRegisterListener(napi_value callback, std::string type)
360 {
361     IMSA_HILOGI("unregister listener: %{public}s.", type.c_str());
362     std::lock_guard<std::recursive_mutex> lock(mutex_);
363     if (jsCbMap_.empty() || jsCbMap_.find(type) == jsCbMap_.end()) {
364         IMSA_HILOGE("methodName: %{public}s already unRegistered!", type.c_str());
365         return;
366     }
367     if (callback == nullptr) {
368         jsCbMap_.erase(type);
369         IMSA_HILOGE("callback is nullptr!");
370         return;
371     }
372 
373     for (auto item = jsCbMap_[type].begin(); item != jsCbMap_[type].end(); item++) {
374         if ((JsUtils::Equals((*item)->env_, callback, (*item)->callback_, (*item)->threadId_))) {
375             jsCbMap_[type].erase(item);
376             break;
377         }
378     }
379     if (jsCbMap_[type].empty()) {
380         jsCbMap_.erase(type);
381     }
382 }
383 
Subscribe(napi_env env,napi_callback_info info)384 napi_value JsGetInputMethodController::Subscribe(napi_env env, napi_callback_info info)
385 {
386     size_t argc = ARGC_TWO;
387     napi_value argv[ARGC_TWO] = { nullptr };
388     napi_value thisVar = nullptr;
389     void *data = nullptr;
390     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, &data));
391     std::string type;
392     // 2 means least param num.
393     PARAM_CHECK_RETURN(env, argc >= 2, "at least two parameters is required!", TYPE_NONE, nullptr);
394     PARAM_CHECK_RETURN(env, JsUtil::GetValue(env, argv[0], type), "type must be string", TYPE_NONE, nullptr);
395     PARAM_CHECK_RETURN(env, EventChecker::IsValidEventType(EventSubscribeModule::INPUT_METHOD_CONTROLLER, type),
396         "type verification failed, review the instructions and fill in the fixed values!", TYPE_NONE, nullptr);
397     PARAM_CHECK_RETURN(env, JsUtil::GetType(env, argv[1]) == napi_function, "callback", TYPE_FUNCTION, nullptr);
398     IMSA_HILOGD("subscribe type: %{public}s.", type.c_str());
399     if (TEXT_EVENT_TYPE.find(type) != TEXT_EVENT_TYPE.end()) {
400         if (!InputMethodController::GetInstance()->WasAttached()) {
401             JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_DETACHED, "need to be attached first", TYPE_NONE);
402             return nullptr;
403         }
404     }
405 
406     auto engine = reinterpret_cast<JsGetInputMethodController *>(JsUtils::GetNativeSelf(env, info));
407     if (engine == nullptr) {
408         return nullptr;
409     }
410     std::shared_ptr<JSCallbackObject> callback =
411         std::make_shared<JSCallbackObject>(env, argv[ARGC_ONE], std::this_thread::get_id());
412     engine->RegisterListener(argv[ARGC_ONE], type, callback);
413 
414     napi_value result = nullptr;
415     napi_get_null(env, &result);
416     return result;
417 }
418 
UnSubscribe(napi_env env,napi_callback_info info)419 napi_value JsGetInputMethodController::UnSubscribe(napi_env env, napi_callback_info info)
420 {
421     size_t argc = ARGC_TWO;
422     napi_value argv[ARGC_TWO] = { nullptr };
423     napi_value thisVar = nullptr;
424     void *data = nullptr;
425     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, &data));
426     std::string type;
427     // 1 means least param num.
428     if (argc < 1 || !JsUtil::GetValue(env, argv[0], type) ||
429         !EventChecker::IsValidEventType(EventSubscribeModule::INPUT_METHOD_CONTROLLER, type)) {
430         IMSA_HILOGE("unsubscribe failed, type: %{public}s!", type.c_str());
431         return nullptr;
432     }
433 
434     // if the second param is not napi_function/napi_null/napi_undefined, return
435     auto paramType = JsUtil::GetType(env, argv[1]);
436     if (paramType != napi_function && paramType != napi_null && paramType != napi_undefined) {
437         return nullptr;
438     }
439     // if the second param is napi_function, delete it, else delete all
440     argv[1] = paramType == napi_function ? argv[1] : nullptr;
441 
442     IMSA_HILOGD("unsubscribe type: %{public}s.", type.c_str());
443     auto engine = reinterpret_cast<JsGetInputMethodController *>(JsUtils::GetNativeSelf(env, info));
444     if (engine == nullptr) {
445         return nullptr;
446     }
447     engine->UnRegisterListener(argv[1], type);
448 
449     napi_value result = nullptr;
450     napi_get_null(env, &result);
451     return result;
452 }
453 
CreateSelectRange(napi_env env,int32_t start,int32_t end)454 napi_value JsGetInputMethodController::CreateSelectRange(napi_env env, int32_t start, int32_t end)
455 {
456     napi_value range = nullptr;
457     napi_create_object(env, &range);
458 
459     napi_value value = nullptr;
460     napi_create_int32(env, start, &value);
461     napi_set_named_property(env, range, "start", value);
462 
463     napi_create_int32(env, end, &value);
464     napi_set_named_property(env, range, "end", value);
465 
466     return range;
467 }
468 
CreateSelectMovement(napi_env env,int32_t direction)469 napi_value JsGetInputMethodController::CreateSelectMovement(napi_env env, int32_t direction)
470 {
471     napi_value movement = nullptr;
472     napi_create_object(env, &movement);
473 
474     napi_value value = nullptr;
475     napi_create_int32(env, direction, &value);
476     napi_set_named_property(env, movement, "direction", value);
477 
478     return movement;
479 }
480 
HandleSoftKeyboard(napi_env env,napi_callback_info info,std::function<int32_t ()> callback,bool isOutput,bool needThrowException)481 napi_value JsGetInputMethodController::HandleSoftKeyboard(
482     napi_env env, napi_callback_info info, std::function<int32_t()> callback, bool isOutput, bool needThrowException)
483 {
484     auto ctxt = std::make_shared<HandleContext>();
485     auto input = [ctxt](
486                      napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status { return napi_ok; };
487     auto output = [ctxt, isOutput](napi_env env, napi_value *result) -> napi_status {
488         if (!isOutput) {
489             return napi_ok;
490         }
491         napi_status status = napi_get_boolean(env, ctxt->isHandle, result);
492         IMSA_HILOGE("output get boolean != nullptr[%{public}d]", result != nullptr);
493         return status;
494     };
495     auto exec = [ctxt, callback, needThrowException](AsyncCall::Context *ctx) {
496         int errCode = callback();
497         if (errCode == ErrorCode::NO_ERROR) {
498             IMSA_HILOGI("exec success.");
499             ctxt->status = napi_ok;
500             ctxt->isHandle = true;
501             ctxt->SetState(ctxt->status);
502             return;
503         }
504         if (needThrowException) {
505             ctxt->SetErrorCode(errCode);
506         }
507     };
508     ctxt->SetAction(std::move(input), std::move(output));
509     // 1 means JsAPI has 1 param at most.
510     AsyncCall asyncCall(env, info, ctxt, 1);
511     return asyncCall.Call(env, exec, "handleSoftKeyboard");
512 }
513 
GetValue(napi_env env,napi_value in,Range & out)514 bool JsGetInputMethodController::GetValue(napi_env env, napi_value in, Range &out)
515 {
516     auto ret = JsUtil::Object::ReadProperty(env, in, "start", out.start);
517     return ret && JsUtil::Object::ReadProperty(env, in, "end", out.end);
518 }
519 
520 /**
521  * let textConfig: TextConfig = {
522  *   inputAttribute: InputAttribute = {
523  *     textInputType: TextInputType = TextInputType.TEXT,
524  *     enterKeyType: EnterKeyType = EnterKeyType.NONE
525  *   },
526  *   cursorInfo?: CursorInfo = {
527  *     left: number,
528  *     top: number,
529  *     width: number,
530  *     height: number,
531  *   },
532  *   selection?: Range = {
533  *     start: number,
534  *     end: number
535  *   },
536  *   windowId?: number
537  * }
538  */
GetValue(napi_env env,napi_value in,TextConfig & out)539 bool JsGetInputMethodController::GetValue(napi_env env, napi_value in, TextConfig &out)
540 {
541     napi_value attributeResult = nullptr;
542     napi_status status = JsUtils::GetValue(env, in, "inputAttribute", attributeResult);
543     CHECK_RETURN(status == napi_ok, "inputAttribute must be InputAttribute!", false);
544     bool ret = JsGetInputMethodController::GetValue(env, attributeResult, out.inputAttribute);
545     CHECK_RETURN(ret, "inputAttribute of TextConfig must be valid!", ret);
546 
547     napi_value cursorInfoResult = nullptr;
548     status = JsUtils::GetValue(env, in, "cursorInfo", cursorInfoResult);
549     bool result = false;
550     if (status == napi_ok) {
551         result = JsGetInputMethodController::GetValue(env, cursorInfoResult, out.cursorInfo);
552         IMSA_HILOGE("get cursorInfo end, ret: %{public}d", result);
553     }
554 
555     napi_value rangeResult = nullptr;
556     status = JsUtils::GetValue(env, in, "selection", rangeResult);
557     if (status == napi_ok) {
558         result = JsGetInputMethodController::GetValue(env, rangeResult, out.range);
559         IMSA_HILOGE("get selectionRange end, ret: %{public}d", result);
560     }
561 
562     result = JsUtil::Object::ReadProperty(env, in, "windowId", out.windowId);
563     IMSA_HILOGE("get windowId end, ret: %{public}d", result);
564     return ret;
565 }
566 
Attach(napi_env env,napi_callback_info info)567 napi_value JsGetInputMethodController::Attach(napi_env env, napi_callback_info info)
568 {
569     InputMethodSyncTrace tracer("JsGetInputMethodController_Attach");
570     auto ctxt = std::make_shared<AttachContext>();
571     auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
572         PARAM_CHECK_RETURN(env, argc > 1, "at least two parameters is required!", TYPE_NONE, napi_generic_failure);
573         PARAM_CHECK_RETURN(env, JsUtil::GetValue(env, argv[0], ctxt->showKeyboard),
574             "showKeyboard covert failed, type must be boolean!", TYPE_NONE, napi_generic_failure);
575         PARAM_CHECK_RETURN(env, JsGetInputMethodController::GetValue(env, argv[1], ctxt->textConfig),
576             "textConfig covert failed, type must be TextConfig!", TYPE_NONE, napi_generic_failure);
577         // requestKeyboardReason not must
578         if (argc > 2) {
579             napi_valuetype valueType = napi_undefined;
580             napi_typeof(env, argv[2], &valueType);
581             if (valueType != napi_function) {
582                 JsUtil::GetValue(env, argv[2], ctxt->requestKeyboardReason);
583             }
584         }
585         return napi_ok;
586     };
587     auto exec = [ctxt, env](AsyncCall::Context *ctx) {
588         ctxt->textListener = JsGetInputMethodTextChangedListener::GetInstance();
589         OHOS::MiscServices::AttachOptions attachOptions;
590         attachOptions.isShowKeyboard = ctxt->showKeyboard;
591         attachOptions.requestKeyboardReason =
592               static_cast<OHOS::MiscServices::RequestKeyboardReason>(ctxt->requestKeyboardReason);
593         auto status = InputMethodController::GetInstance()->Attach(
594             ctxt->textListener, attachOptions, ctxt->textConfig);
595         ctxt->SetErrorCode(status);
596         CHECK_RETURN_VOID(status == ErrorCode::NO_ERROR, "attach return error!");
597         ctxt->SetState(napi_ok);
598     };
599     ctxt->SetAction(std::move(input));
600     // 3 means JsAPI:attach has 3 params at most.
601     AsyncCall asyncCall(env, info, ctxt, 3);
602     return asyncCall.Call(env, exec, "attach");
603 }
604 
Detach(napi_env env,napi_callback_info info)605 napi_value JsGetInputMethodController::Detach(napi_env env, napi_callback_info info)
606 {
607     return HandleSoftKeyboard(
608         env, info, [] { return InputMethodController::GetInstance()->Close(); }, false, true);
609 }
610 
ShowTextInput(napi_env env,napi_callback_info info)611 napi_value JsGetInputMethodController::ShowTextInput(napi_env env, napi_callback_info info)
612 {
613     IMSA_HILOGI("run in.");
614     AttachOptions attachOptions;
615     JsGetInputMethodController::GetAttachOptionsValue(env, info, attachOptions);
616     InputMethodSyncTrace tracer("JsGetInputMethodController_ShowTextInput");
617     return HandleSoftKeyboard(
618         env, info,
619         [attachOptions] {
620             return InputMethodController::GetInstance()->ShowTextInput(attachOptions);
621         },
622         false, true);
623 }
624 
GetAttachOptionsValue(napi_env env,napi_callback_info cbinfo,AttachOptions & attachOptions)625 napi_value JsGetInputMethodController::GetAttachOptionsValue(
626     napi_env env, napi_callback_info cbinfo, AttachOptions &attachOptions)
627 {
628     napi_value result = nullptr;
629     NAPI_CALL(env, napi_create_int32(env, 0, &result));
630     size_t argc = ARGC_ONE;
631     napi_value argv[ARGC_ONE] = { nullptr };
632     napi_value thisVar = nullptr;
633     void *data = nullptr;
634     NAPI_CALL(env, napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data));
635     int32_t requestKeyboardReason = 0;
636     if (argc > 0) {
637         napi_valuetype valueType = napi_undefined;
638         napi_typeof(env, argv[0], &valueType);
639         if (valueType != napi_function) {
640             JsUtil::GetValue(env, argv[0], requestKeyboardReason);
641         }
642     }
643     IMSA_HILOGI("run in. requestKeyboardReason=%{public}d", requestKeyboardReason);
644     attachOptions.requestKeyboardReason = static_cast<OHOS::MiscServices::RequestKeyboardReason>(requestKeyboardReason);
645 
646     return result;
647 }
648 
HideTextInput(napi_env env,napi_callback_info info)649 napi_value JsGetInputMethodController::HideTextInput(napi_env env, napi_callback_info info)
650 {
651     InputMethodSyncTrace tracer("JsGetInputMethodController_HideTextInput");
652     return HandleSoftKeyboard(
653         env, info, [] { return InputMethodController::GetInstance()->HideTextInput(); }, false, true);
654 }
655 
SetCallingWindow(napi_env env,napi_callback_info info)656 napi_value JsGetInputMethodController::SetCallingWindow(napi_env env, napi_callback_info info)
657 {
658     auto ctxt = std::make_shared<SetCallingWindowContext>();
659     auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
660         PARAM_CHECK_RETURN(env, argc > 0, "at least one parameter is required!", TYPE_NONE, napi_generic_failure);
661         // 0 means the first parameter: windowId
662         napi_status status = JsUtils::GetValue(env, argv[0], ctxt->windID);
663         PARAM_CHECK_RETURN(env, status == napi_ok, "windowId type must be number", TYPE_NONE, status);
664         return status;
665     };
666     auto exec = [ctxt](AsyncCall::Context *ctx) {
667         auto errcode = InputMethodController::GetInstance()->SetCallingWindow(ctxt->windID);
668         ctxt->SetErrorCode(errcode);
669         CHECK_RETURN_VOID(errcode == ErrorCode::NO_ERROR, "setCallingWindow return error!");
670         ctxt->SetState(napi_ok);
671     };
672     ctxt->SetAction(std::move(input));
673     // 2 means JsAPI:setCallingWindow has 2 params at most.
674     AsyncCall asyncCall(env, info, ctxt, 2);
675     return asyncCall.Call(env, exec, "setCallingWindow");
676 }
677 
GetValue(napi_env env,napi_value in,CursorInfo & out)678 bool JsGetInputMethodController::GetValue(napi_env env, napi_value in, CursorInfo &out)
679 {
680     auto ret = JsUtil::Object::ReadProperty(env, in, "left", out.left);
681     ret = ret && JsUtil::Object::ReadProperty(env, in, "top", out.top);
682     ret = ret && JsUtil::Object::ReadProperty(env, in, "width", out.width);
683     return ret && JsUtil::Object::ReadProperty(env, in, "height", out.height);
684 }
685 
UpdateCursor(napi_env env,napi_callback_info info)686 napi_value JsGetInputMethodController::UpdateCursor(napi_env env, napi_callback_info info)
687 {
688     auto ctxt = std::make_shared<UpdateCursorContext>();
689     auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
690         PARAM_CHECK_RETURN(env, argc > 0, "at least one parameter is required!", TYPE_NONE, napi_generic_failure);
691         // 0 means the first parameter: cursorInfo
692         PARAM_CHECK_RETURN(env, JsUtil::GetType(env, argv[0]) == napi_object,
693             "cursorInfo type must be CursorInfo", TYPE_NONE, napi_generic_failure);
694         bool ret = JsGetInputMethodController::GetValue(env, argv[0], ctxt->cursorInfo);
695         PARAM_CHECK_RETURN(env, ret, "cursorInfo covert failed, must contain four numbers!", TYPE_NONE,
696             napi_generic_failure);
697         return napi_ok;
698     };
699     auto exec = [ctxt](AsyncCall::Context *ctx) {
700         auto errcode = InputMethodController::GetInstance()->OnCursorUpdate(ctxt->cursorInfo);
701         ctxt->SetErrorCode(errcode);
702         CHECK_RETURN_VOID(errcode == ErrorCode::NO_ERROR, "updateCursor return error!");
703         ctxt->SetState(napi_ok);
704     };
705     ctxt->SetAction(std::move(input));
706     // 2 means JsAPI:updateCursor has 2 params at most.
707     AsyncCall asyncCall(env, info, ctxt, 2);
708     return asyncCall.Call(env, exec, "updateCursor");
709 }
710 
ChangeSelection(napi_env env,napi_callback_info info)711 napi_value JsGetInputMethodController::ChangeSelection(napi_env env, napi_callback_info info)
712 {
713     std::shared_ptr<ChangeSelectionContext> ctxt = std::make_shared<ChangeSelectionContext>();
714     auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
715         PARAM_CHECK_RETURN(env, argc > 2, "at least three parameters is required!", TYPE_NONE, napi_generic_failure);
716         PARAM_CHECK_RETURN(env, JsUtil::GetValue(env, argv[0], ctxt->text), "text type must be string!",
717             TYPE_NONE, napi_generic_failure);
718         PARAM_CHECK_RETURN(env, JsUtil::GetValue(env, argv[1], ctxt->start), "start type must be number!",
719             TYPE_NONE, napi_generic_failure);
720         PARAM_CHECK_RETURN(env, JsUtil::GetValue(env, argv[2], ctxt->end), "end type must be number!", TYPE_NONE,
721             napi_generic_failure);
722         return napi_ok;
723     };
724     auto exec = [ctxt](AsyncCall::Context *ctx) {
725         auto errcode = InputMethodController::GetInstance()->OnSelectionChange(ctxt->text, ctxt->start, ctxt->end);
726         ctxt->SetErrorCode(errcode);
727         CHECK_RETURN_VOID(errcode == ErrorCode::NO_ERROR, "changeSelection return error!");
728         ctxt->SetState(napi_ok);
729     };
730     ctxt->SetAction(std::move(input));
731     // 4 means JsAPI:changeSelection has 4 params at most.
732     AsyncCall asyncCall(env, info, ctxt, 4);
733     return asyncCall.Call(env, exec, "changeSelection");
734 }
735 
GetValue(napi_env env,napi_value in,InputAttribute & out)736 bool JsGetInputMethodController::GetValue(napi_env env, napi_value in, InputAttribute &out)
737 {
738     auto ret = JsUtil::Object::ReadProperty(env, in, "textInputType", out.inputPattern);
739     return ret && JsUtil::Object::ReadProperty(env, in, "enterKeyType", out.enterKeyType);
740 }
741 
UpdateAttribute(napi_env env,napi_callback_info info)742 napi_value JsGetInputMethodController::UpdateAttribute(napi_env env, napi_callback_info info)
743 {
744     auto ctxt = std::make_shared<UpdateAttributeContext>();
745     auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
746         PARAM_CHECK_RETURN(env, argc > 0, "at least one parameter is required!", TYPE_NONE, napi_generic_failure);
747         bool ret = JsGetInputMethodController::GetValue(env, argv[0], ctxt->attribute);
748         PARAM_CHECK_RETURN(env, ret, "attribute type must be InputAttribute!", TYPE_NONE, napi_generic_failure);
749         ctxt->configuration.SetTextInputType(static_cast<TextInputType>(ctxt->attribute.inputPattern));
750         ctxt->configuration.SetEnterKeyType(static_cast<EnterKeyType>(ctxt->attribute.enterKeyType));
751         return napi_ok;
752     };
753     auto exec = [ctxt](AsyncCall::Context *ctx) {
754         auto errcode = InputMethodController::GetInstance()->OnConfigurationChange(ctxt->configuration);
755         ctxt->SetErrorCode(errcode);
756         CHECK_RETURN_VOID(errcode == ErrorCode::NO_ERROR, "updateAttribute return error!");
757         ctxt->SetState(napi_ok);
758     };
759     ctxt->SetAction(std::move(input));
760     // 2 means JsAPI:updateAttribute has 2 params at most.
761     AsyncCall asyncCall(env, info, ctxt, 2);
762     return asyncCall.Call(env, exec, "updateAttribute");
763 }
764 
ShowSoftKeyboard(napi_env env,napi_callback_info info)765 napi_value JsGetInputMethodController::ShowSoftKeyboard(napi_env env, napi_callback_info info)
766 {
767     InputMethodSyncTrace tracer("JsGetInputMethodController_ShowSoftKeyboard");
768     return HandleSoftKeyboard(
769         env, info, [] { return InputMethodController::GetInstance()->ShowSoftKeyboard(); }, false, true);
770 }
771 
HideSoftKeyboard(napi_env env,napi_callback_info info)772 napi_value JsGetInputMethodController::HideSoftKeyboard(napi_env env, napi_callback_info info)
773 {
774     InputMethodSyncTrace tracer("JsGetInputMethodController_HideSoftKeyboard");
775     return HandleSoftKeyboard(
776         env, info, [] { return InputMethodController::GetInstance()->HideSoftKeyboard(); }, false, true);
777 }
778 
StopInputSession(napi_env env,napi_callback_info info)779 napi_value JsGetInputMethodController::StopInputSession(napi_env env, napi_callback_info info)
780 {
781     return HandleSoftKeyboard(
782         env, info, [] { return InputMethodController::GetInstance()->StopInputSession(); }, true, true);
783 }
784 
StopInput(napi_env env,napi_callback_info info)785 napi_value JsGetInputMethodController::StopInput(napi_env env, napi_callback_info info)
786 {
787     return HandleSoftKeyboard(
788         env, info, [] { return InputMethodController::GetInstance()->HideCurrentInput(); }, true, false);
789 }
790 
OnSelectByRange(int32_t start,int32_t end)791 void JsGetInputMethodController::OnSelectByRange(int32_t start, int32_t end)
792 {
793     std::string type = "selectByRange";
794     auto entry = GetEntry("selectByRange", [start, end](UvEntry &entry) {
795         entry.start = start;
796         entry.end = end;
797     });
798     if (entry == nullptr) {
799         IMSA_HILOGD("entry is nullptr.");
800         return;
801     }
802     auto eventHandler = GetEventHandler();
803     if (eventHandler == nullptr) {
804         IMSA_HILOGE("eventHandler is nullptr!");
805         return;
806     }
807     auto task = [entry]() {
808         auto getProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
809             if (argc < ARGC_ONE) {
810                 return false;
811             }
812             napi_value range = CreateSelectRange(env, entry->start, entry->end);
813             if (range == nullptr) {
814                 IMSA_HILOGE("set select range failed!");
815                 return false;
816             }
817             // 0 means the first param of callback.
818             args[0] = range;
819             return true;
820         };
821         // 1 means the callback has one param.
822         JsCallbackHandler::Traverse(entry->vecCopy, { 1, getProperty });
823     };
824     eventHandler->PostTask(task, type);
825 }
826 
OnSelectByMovement(int32_t direction)827 void JsGetInputMethodController::OnSelectByMovement(int32_t direction)
828 {
829     std::string type = "selectByMovement";
830     auto entry = GetEntry(type, [direction](UvEntry &entry) { entry.direction = direction; });
831     if (entry == nullptr) {
832         IMSA_HILOGE("failed to get uv entry!");
833         return;
834     }
835     auto eventHandler = GetEventHandler();
836     if (eventHandler == nullptr) {
837         IMSA_HILOGE("eventHandler is nullptr!");
838         return;
839     }
840     IMSA_HILOGI("direction: %{public}d.", direction);
841     auto task = [entry]() {
842         auto getProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
843             if (argc < 1) {
844                 return false;
845             }
846             napi_value movement = CreateSelectMovement(env, entry->direction);
847             if (movement == nullptr) {
848                 IMSA_HILOGE("set select movement failed!");
849                 return false;
850             }
851             // 0 means the first param of callback.
852             args[0] = movement;
853             return true;
854         };
855         // 1 means the callback has one param.
856         JsCallbackHandler::Traverse(entry->vecCopy, { 1, getProperty });
857     };
858     eventHandler->PostTask(task, type);
859 }
860 
InsertText(const std::u16string & text)861 void JsGetInputMethodController::InsertText(const std::u16string &text)
862 {
863     std::string insertText = Str16ToStr8(text);
864     std::string type = "insertText";
865     auto entry = GetEntry(type, [&insertText](UvEntry &entry) { entry.text = insertText; });
866     if (entry == nullptr) {
867         IMSA_HILOGD("failed to get uv entry.");
868         return;
869     }
870     auto eventHandler = GetEventHandler();
871     if (eventHandler == nullptr) {
872         IMSA_HILOGE("eventHandler is nullptr!");
873         return;
874     }
875     IMSA_HILOGI("start.");
876     auto task = [entry]() {
877         auto getInsertTextProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
878             if (argc == ARGC_ZERO) {
879                 IMSA_HILOGE("getInsertTextProperty the number of argc is invalid.");
880                 return false;
881             }
882             // 0 means the first param of callback.
883             napi_create_string_utf8(env, entry->text.c_str(), NAPI_AUTO_LENGTH, &args[0]);
884             return true;
885         };
886         // 1 means the callback has one param.
887         JsCallbackHandler::Traverse(entry->vecCopy, { 1, getInsertTextProperty });
888     };
889     eventHandler->PostTask(task, type);
890 }
891 
DeleteRight(int32_t length)892 void JsGetInputMethodController::DeleteRight(int32_t length)
893 {
894     std::string type = "deleteRight";
895     auto entry = GetEntry(type, [&length](UvEntry &entry) { entry.length = length; });
896     if (entry == nullptr) {
897         IMSA_HILOGD("failed to get uv entry.");
898         return;
899     }
900     auto eventHandler = GetEventHandler();
901     if (eventHandler == nullptr) {
902         IMSA_HILOGE("eventHandler is nullptr!");
903         return;
904     }
905     IMSA_HILOGI("length: %{public}d", length);
906 
907     auto task = [entry]() {
908         auto getDeleteForwardProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
909             if (argc == ARGC_ZERO) {
910                 IMSA_HILOGE("getDeleteForwardProperty the number of argc is invalid.");
911                 return false;
912             }
913             // 0 means the first param of callback.
914             napi_create_int32(env, entry->length, &args[0]);
915             return true;
916         };
917         // 1 means the callback has one param.
918         JsCallbackHandler::Traverse(entry->vecCopy, { 1, getDeleteForwardProperty });
919     };
920     eventHandler->PostTask(task, type);
921 }
922 
DeleteLeft(int32_t length)923 void JsGetInputMethodController::DeleteLeft(int32_t length)
924 {
925     std::string type = "deleteLeft";
926     auto entry = GetEntry(type, [&length](UvEntry &entry) { entry.length = length; });
927     if (entry == nullptr) {
928         IMSA_HILOGD("failed to get uv entry.");
929         return;
930     }
931     auto eventHandler = GetEventHandler();
932     if (eventHandler == nullptr) {
933         IMSA_HILOGE("eventHandler is nullptr!");
934         return;
935     }
936     IMSA_HILOGI("length: %{public}d", length);
937     auto task = [entry]() {
938         auto getDeleteBackwardProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
939             if (argc == ARGC_ZERO) {
940                 IMSA_HILOGE("getDeleteBackwardProperty the number of argc is invalid.");
941                 return false;
942             }
943             // 0 means the first param of callback.
944             napi_create_int32(env, entry->length, &args[0]);
945             return true;
946         };
947         // 1 means the callback has one param.
948         JsCallbackHandler::Traverse(entry->vecCopy, { 1, getDeleteBackwardProperty });
949     };
950     eventHandler->PostTask(task, type);
951 }
952 
SendKeyboardStatus(const KeyboardStatus & status)953 void JsGetInputMethodController::SendKeyboardStatus(const KeyboardStatus &status)
954 {
955     std::string type = "sendKeyboardStatus";
956     auto entry = GetEntry(type, [&status](UvEntry &entry) { entry.keyboardStatus = static_cast<int32_t>(status); });
957     if (entry == nullptr) {
958         IMSA_HILOGD("failed to get uv entry.");
959         return;
960     }
961     auto eventHandler = GetEventHandler();
962     if (eventHandler == nullptr) {
963         IMSA_HILOGE("eventHandler is nullptr!");
964         return;
965     }
966     IMSA_HILOGI("status: %{public}d", static_cast<int32_t>(status));
967     auto task = [entry]() {
968         auto getSendKeyboardStatusProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
969             if (argc == ARGC_ZERO) {
970                 IMSA_HILOGE("getSendKeyboardStatusProperty the number of argc is invalid.");
971                 return false;
972             }
973             // 0 means the first param of callback.
974             napi_create_int32(env, entry->keyboardStatus, &args[0]);
975             return true;
976         };
977         // 1 means the callback has one param.
978         JsCallbackHandler::Traverse(entry->vecCopy, { 1, getSendKeyboardStatusProperty });
979     };
980     eventHandler->PostTask(task, type);
981 }
982 
CreateSendFunctionKey(napi_env env,int32_t functionKey)983 napi_value JsGetInputMethodController::CreateSendFunctionKey(napi_env env, int32_t functionKey)
984 {
985     napi_value functionkey = nullptr;
986     napi_create_object(env, &functionkey);
987 
988     napi_value value = nullptr;
989     napi_create_int32(env, functionKey, &value);
990     napi_set_named_property(env, functionkey, "enterKeyType", value);
991 
992     return functionkey;
993 }
994 
SendFunctionKey(const FunctionKey & functionKey)995 void JsGetInputMethodController::SendFunctionKey(const FunctionKey &functionKey)
996 {
997     std::string type = "sendFunctionKey";
998     auto entry = GetEntry(type,
999         [&functionKey](UvEntry &entry) { entry.enterKeyType = static_cast<int32_t>(functionKey.GetEnterKeyType()); });
1000     if (entry == nullptr) {
1001         IMSA_HILOGD("failed to get uv entry.");
1002         return;
1003     }
1004     auto eventHandler = GetEventHandler();
1005     if (eventHandler == nullptr) {
1006         IMSA_HILOGE("eventHandler is nullptr!");
1007         return;
1008     }
1009     IMSA_HILOGI("functionKey: %{public}d", static_cast<int32_t>(functionKey.GetEnterKeyType()));
1010     auto task = [entry]() {
1011         auto getSendFunctionKeyProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
1012             if (argc == ARGC_ZERO) {
1013                 IMSA_HILOGE("getSendFunctionKeyProperty the number of argc is invalid.");
1014                 return false;
1015             }
1016             napi_value functionKey = CreateSendFunctionKey(env, entry->enterKeyType);
1017             if (functionKey == nullptr) {
1018                 IMSA_HILOGE("set select movement failed");
1019                 return false;
1020             }
1021             // 0 means the first param of callback.
1022             args[0] = functionKey;
1023             return true;
1024         };
1025         // 1 means the callback has one param.
1026         JsCallbackHandler::Traverse(entry->vecCopy, { 1, getSendFunctionKeyProperty });
1027     };
1028     eventHandler->PostTask(task, type);
1029 }
1030 
MoveCursor(const Direction direction)1031 void JsGetInputMethodController::MoveCursor(const Direction direction)
1032 {
1033     std::string type = "moveCursor";
1034     auto entry = GetEntry(type, [&direction](UvEntry &entry) { entry.direction = static_cast<int32_t>(direction); });
1035     if (entry == nullptr) {
1036         IMSA_HILOGD("failed to get uv entry.");
1037         return;
1038     }
1039     auto eventHandler = GetEventHandler();
1040     if (eventHandler == nullptr) {
1041         IMSA_HILOGE("eventHandler is nullptr!");
1042         return;
1043     }
1044     IMSA_HILOGI("direction: %{public}d", static_cast<int32_t>(direction));
1045     auto task = [entry]() {
1046         auto getMoveCursorProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
1047             if (argc == ARGC_ZERO) {
1048                 IMSA_HILOGE("getMoveCursorProperty the number of argc is invalid.");
1049                 return false;
1050             }
1051             // 0 means the first param of callback.
1052             napi_create_int32(env, static_cast<int32_t>(entry->direction), &args[0]);
1053             return true;
1054         };
1055         // 1 means the callback has one param.
1056         JsCallbackHandler::Traverse(entry->vecCopy, { 1, getMoveCursorProperty });
1057     };
1058     eventHandler->PostTask(task, type);
1059 }
1060 
HandleExtendAction(int32_t action)1061 void JsGetInputMethodController::HandleExtendAction(int32_t action)
1062 {
1063     std::string type = "handleExtendAction";
1064     auto entry = GetEntry(type, [&action](UvEntry &entry) { entry.action = action; });
1065     if (entry == nullptr) {
1066         IMSA_HILOGD("failed to get uv entry.");
1067         return;
1068     }
1069     auto eventHandler = GetEventHandler();
1070     if (eventHandler == nullptr) {
1071         IMSA_HILOGE("eventHandler is nullptr!");
1072         return;
1073     }
1074     IMSA_HILOGI("action: %{public}d", action);
1075     auto task = [entry]() {
1076         auto getHandleExtendActionProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
1077             if (argc == ARGC_ZERO) {
1078                 IMSA_HILOGE("getHandleExtendActionProperty the number of argc is invalid.");
1079                 return false;
1080             }
1081             // 0 means the first param of callback.
1082             napi_create_int32(env, entry->action, &args[0]);
1083             return true;
1084         };
1085         // 1 means the callback has one param.
1086         JsCallbackHandler::Traverse(entry->vecCopy, { 1, getHandleExtendActionProperty });
1087     };
1088     eventHandler->PostTask(task, type);
1089 }
1090 
GetText(const std::string & type,int32_t number)1091 std::u16string JsGetInputMethodController::GetText(const std::string &type, int32_t number)
1092 {
1093     auto textResultHandler = std::make_shared<BlockData<std::string>>(MAX_TIMEOUT, "");
1094     auto entry = GetEntry(type, [&number, textResultHandler](UvEntry &entry) {
1095         entry.number = number;
1096         entry.textResultHandler = textResultHandler;
1097     });
1098     if (entry == nullptr) {
1099         IMSA_HILOGE("failed to get uv entry.");
1100         return u"";
1101     }
1102     auto eventHandler = GetEventHandler();
1103     if (eventHandler == nullptr) {
1104         IMSA_HILOGE("eventHandler is nullptr!");
1105         return u"";
1106     }
1107     IMSA_HILOGI("type: %{public}s, number: %{public}d.", type.c_str(), number);
1108     auto task = [entry]() {
1109         auto fillArguments = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
1110             if (argc < 1) {
1111                 IMSA_HILOGE("argc is err.");
1112                 return false;
1113             }
1114             // 0 means the first param of callback.
1115             napi_create_int32(env, entry->number, &args[0]);
1116             return true;
1117         };
1118         std::string text;
1119         // 1 means callback has one param.
1120         JsCallbackHandler::Traverse(entry->vecCopy, { 1, fillArguments }, text);
1121         entry->textResultHandler->SetValue(text);
1122     };
1123     eventHandler->PostTask(task, type);
1124     return Str8ToStr16(textResultHandler->GetValue());
1125 }
1126 
GetTextIndexAtCursor()1127 int32_t JsGetInputMethodController::GetTextIndexAtCursor()
1128 {
1129     std::string type = "getTextIndexAtCursor";
1130     auto indexResultHandler = std::make_shared<BlockData<int32_t>>(MAX_TIMEOUT, -1);
1131     auto entry =
1132         GetEntry(type, [indexResultHandler](UvEntry &entry) { entry.indexResultHandler = indexResultHandler; });
1133     if (entry == nullptr) {
1134         IMSA_HILOGE("failed to get uv entry!");
1135         return -1;
1136     }
1137     auto eventHandler = GetEventHandler();
1138     if (eventHandler == nullptr) {
1139         IMSA_HILOGE("eventHandler is nullptr!");
1140         return -1;
1141     }
1142     IMSA_HILOGI("run in");
1143     auto task = [entry]() {
1144         int32_t index = -1;
1145         // 0 means callback has no params.
1146         JsCallbackHandler::Traverse(entry->vecCopy, { 0, nullptr }, index);
1147         entry->indexResultHandler->SetValue(index);
1148     };
1149     eventHandler->PostTask(task, type);
1150     return indexResultHandler->GetValue();
1151 }
1152 
GetEventHandler()1153 std::shared_ptr<AppExecFwk::EventHandler> JsGetInputMethodController::GetEventHandler()
1154 {
1155     std::lock_guard<std::mutex> lock(eventHandlerMutex_);
1156     return handler_;
1157 }
1158 
GetEntry(const std::string & type,EntrySetter entrySetter)1159 std::shared_ptr<JsGetInputMethodController::UvEntry> JsGetInputMethodController::GetEntry(
1160     const std::string &type, EntrySetter entrySetter)
1161 {
1162     IMSA_HILOGD("type: %{public}s", type.c_str());
1163     std::shared_ptr<UvEntry> entry = nullptr;
1164     {
1165         std::lock_guard<std::recursive_mutex> lock(mutex_);
1166         if (jsCbMap_[type].empty()) {
1167             IMSA_HILOGD("%{public}s cb-vector is empty.", type.c_str());
1168             return nullptr;
1169         }
1170         entry = std::make_shared<UvEntry>(jsCbMap_[type], type);
1171     }
1172     if (entrySetter != nullptr) {
1173         entrySetter(*entry);
1174     }
1175     return entry;
1176 }
1177 
SendMessage(napi_env env,napi_callback_info info)1178 napi_value JsGetInputMethodController::SendMessage(napi_env env, napi_callback_info info)
1179 {
1180     auto ctxt = std::make_shared<SendMessageContext>();
1181     auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
1182         PARAM_CHECK_RETURN(env, argc > 0, "at least one parameter is required!", TYPE_NONE, napi_generic_failure);
1183         PARAM_CHECK_RETURN(env, JsUtil::GetType(env, argv[0]) == napi_string, "msgId",
1184             TYPE_STRING, napi_generic_failure);
1185         CHECK_RETURN(JsUtils::GetValue(env, argv[0], ctxt->arrayBuffer.msgId) == napi_ok,
1186             "msgId covert failed!", napi_generic_failure);
1187         ctxt->arrayBuffer.jsArgc = argc;
1188         // 1 means first param msgId.
1189         if (argc > 1) {
1190             bool isArryBuffer = false;
1191             //  1 means second param msgParam index.
1192             CHECK_RETURN(napi_is_arraybuffer(env, argv[1], &isArryBuffer) == napi_ok,
1193                 "napi_is_arraybuffer failed!", napi_generic_failure);
1194             PARAM_CHECK_RETURN(env, isArryBuffer, "msgParam", TYPE_ARRAY_BUFFER, napi_generic_failure);
1195             CHECK_RETURN(JsUtils::GetValue(env, argv[1], ctxt->arrayBuffer.msgParam) == napi_ok,
1196                 "msgParam covert failed!", napi_generic_failure);
1197         }
1198         PARAM_CHECK_RETURN(env, ArrayBuffer::IsSizeValid(ctxt->arrayBuffer),
1199             "msgId limit 256B and msgParam limit 128KB.", TYPE_NONE, napi_generic_failure);
1200         ctxt->info = { std::chrono::system_clock::now(), ctxt->arrayBuffer };
1201         messageHandlerQueue_.Push(ctxt->info);
1202         return napi_ok;
1203     };
1204     auto exec = [ctxt](AsyncCall::Context *ctx) {
1205         messageHandlerQueue_.Wait(ctxt->info);
1206         int32_t code = InputMethodController::GetInstance()->SendMessage(ctxt->arrayBuffer);
1207         messageHandlerQueue_.Pop();
1208         if (code == ErrorCode::NO_ERROR) {
1209             ctxt->status = napi_ok;
1210             ctxt->SetState(ctxt->status);
1211         } else {
1212             ctxt->SetErrorCode(code);
1213         }
1214     };
1215     ctxt->SetAction(std::move(input), nullptr);
1216     // 2 means JsAPI:sendMessage has 2 params at most.
1217     AsyncCall asyncCall(env, info, ctxt, 2);
1218     return asyncCall.Call(env, exec, "imcSendMessage");
1219 }
1220 
RecvMessage(napi_env env,napi_callback_info info)1221 napi_value JsGetInputMethodController::RecvMessage(napi_env env, napi_callback_info info)
1222 {
1223     size_t argc = ARGC_ONE;
1224     napi_value argv[ARGC_TWO] = {nullptr};
1225     napi_value thisVar = nullptr;
1226     void *data = nullptr;
1227     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, &data));
1228     std::string type;
1229     if (argc < 0) {
1230         IMSA_HILOGE("RecvMessage failed! argc abnormal.");
1231         return nullptr;
1232     }
1233     if (argc == 0) {
1234         IMSA_HILOGI("RecvMessage off.");
1235         InputMethodController::GetInstance()->RegisterMsgHandler();
1236         return nullptr;
1237     }
1238     IMSA_HILOGI("RecvMessage on.");
1239     PARAM_CHECK_RETURN(env, JsUtil::GetType(env, argv[0]) == napi_object, "msgHnadler (MessageHandler)",
1240         TYPE_OBJECT, nullptr);
1241 
1242     napi_value onMessage = nullptr;
1243     CHECK_RETURN(napi_get_named_property(env, argv[0], "onMessage", &onMessage) == napi_ok,
1244         "Get onMessage property failed!", nullptr);
1245     CHECK_RETURN(JsUtil::GetType(env, onMessage) == napi_function, "onMessage is not napi_function!", nullptr);
1246     napi_value onTerminated = nullptr;
1247     CHECK_RETURN(napi_get_named_property(env, argv[0], "onTerminated", &onTerminated) == napi_ok,
1248         "Get onTerminated property failed!", nullptr);
1249     CHECK_RETURN(JsUtil::GetType(env, onTerminated) == napi_function, "onTerminated is not napi_function!", nullptr);
1250 
1251     std::shared_ptr<MsgHandlerCallbackInterface> callback =
1252         std::make_shared<JsGetInputMethodController::JsMessageHandler>(env, onTerminated, onMessage);
1253     InputMethodController::GetInstance()->RegisterMsgHandler(callback);
1254     napi_value result = nullptr;
1255     napi_get_null(env, &result);
1256     return result;
1257 }
1258 
OnTerminated()1259 int32_t JsGetInputMethodController::JsMessageHandler::OnTerminated()
1260 {
1261     std::lock_guard<decltype(callbackObjectMutex_)> lock(callbackObjectMutex_);
1262     if (jsMessageHandler_ == nullptr) {
1263         IMSA_HILOGI("jsCallbackObject is nullptr, can not call OnTerminated!.");
1264         return ErrorCode::ERROR_NULL_POINTER;
1265     }
1266     auto eventHandler = jsMessageHandler_->GetEventHandler();
1267     if (eventHandler == nullptr) {
1268         IMSA_HILOGI("EventHandler is nullptr!.");
1269         return ErrorCode::ERROR_NULL_POINTER;
1270     }
1271     // Ensure jsMessageHandler_ destructor run in current thread.
1272     auto task = [jsCallback = std::move(jsMessageHandler_)]() {
1273         napi_value callback = nullptr;
1274         napi_value global = nullptr;
1275         if (jsCallback == nullptr) {
1276             IMSA_HILOGI("jsCallback is nullptr!.");
1277             return;
1278         }
1279         napi_get_reference_value(jsCallback->env_, jsCallback->onTerminatedCallback_, &callback);
1280         if (callback != nullptr) {
1281             napi_get_global(jsCallback->env_, &global);
1282             napi_value output = nullptr;
1283             // 0 means the callback has no param.
1284             auto status = napi_call_function(jsCallback->env_, global, callback, 0, nullptr, &output);
1285             if (status != napi_ok) {
1286                 IMSA_HILOGI("Call js function failed!.");
1287                 output = nullptr;
1288             }
1289         }
1290     };
1291     eventHandler->PostTask(task, "IMC_MsgHandler_OnTerminated", 0, AppExecFwk::EventQueue::Priority::VIP);
1292     return ErrorCode::NO_ERROR;
1293 }
1294 
OnMessage(const ArrayBuffer & arrayBuffer)1295 int32_t JsGetInputMethodController::JsMessageHandler::OnMessage(const ArrayBuffer &arrayBuffer)
1296 {
1297     std::lock_guard<decltype(callbackObjectMutex_)> lock(callbackObjectMutex_);
1298     if (jsMessageHandler_ == nullptr) {
1299         IMSA_HILOGI("jsCallbackObject is nullptr, can not call OnTerminated!.");
1300         return ErrorCode::ERROR_NULL_POINTER;
1301     }
1302     auto eventHandler = jsMessageHandler_->GetEventHandler();
1303     if (eventHandler == nullptr) {
1304         IMSA_HILOGI("EventHandler is nullptr!.");
1305         return ErrorCode::ERROR_CLIENT_NULL_POINTER;
1306     }
1307     auto task = [jsCallbackObject = jsMessageHandler_, arrayBuffer]() {
1308         napi_value callback = nullptr;
1309         napi_value global = nullptr;
1310         if (jsCallbackObject == nullptr) {
1311             IMSA_HILOGI("jsCallbackObject is nullptr!.");
1312             return;
1313         }
1314         napi_get_reference_value(jsCallbackObject->env_, jsCallbackObject->onMessageCallback_, &callback);
1315         if (callback != nullptr) {
1316             napi_get_global(jsCallbackObject->env_, &global);
1317             napi_value output = nullptr;
1318             napi_value argv[ARGC_TWO] = { nullptr };
1319             if (JsUtils::GetMessageHandlerCallbackParam(argv, jsCallbackObject, arrayBuffer) != napi_ok) {
1320                 IMSA_HILOGE("Get message handler callback param failed!.");
1321                 return;
1322             }
1323             // The maximum valid parameters count of callback is 2.
1324             auto callbackArgc = arrayBuffer.jsArgc > ARGC_ONE ? ARGC_TWO : ARGC_ONE;
1325             auto status = napi_call_function(jsCallbackObject->env_, global, callback, callbackArgc, argv, &output);
1326             if (status != napi_ok) {
1327                 IMSA_HILOGI("Call js function failed!.");
1328                 output = nullptr;
1329             }
1330         }
1331     };
1332     eventHandler->PostTask(task, "IMC_MsgHandler_OnMessage", 0, AppExecFwk::EventQueue::Priority::VIP);
1333     return ErrorCode::NO_ERROR;
1334 }
1335 } // namespace MiscServices
1336 } // namespace OHOS