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