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