1 /*
2 * Copyright (c) 2022-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
16 #include "js_text_input_client_engine.h"
17
18 #include "input_method_ability.h"
19 #include "inputmethod_trace.h"
20 #include "js_util.h"
21 #include "js_utils.h"
22 #include "napi/native_api.h"
23 #include "napi/native_node_api.h"
24 #include "string_ex.h"
25
26 namespace OHOS {
27 namespace MiscServices {
28 using namespace std::chrono;
29 thread_local napi_ref JsTextInputClientEngine::TICRef_ = nullptr;
30 const std::string JsTextInputClientEngine::TIC_CLASS_NAME = "TextInputClient";
31 constexpr int32_t MAX_WAIT_TIME = 5000;
32 BlockQueue<EditorEventInfo> JsTextInputClientEngine::editorQueue_{ MAX_WAIT_TIME };
Init(napi_env env,napi_value info)33 napi_value JsTextInputClientEngine::Init(napi_env env, napi_value info)
34 {
35 IMSA_HILOGD("JsTextInputClientEngine init");
36 napi_property_descriptor properties[] = {
37 DECLARE_NAPI_FUNCTION("sendKeyFunction", SendKeyFunction),
38 DECLARE_NAPI_FUNCTION("deleteForward", DeleteForward),
39 DECLARE_NAPI_FUNCTION("deleteBackward", DeleteBackward),
40 DECLARE_NAPI_FUNCTION("insertText", InsertText),
41 DECLARE_NAPI_FUNCTION("getForward", GetForward),
42 DECLARE_NAPI_FUNCTION("getBackward", GetBackward),
43 DECLARE_NAPI_FUNCTION("getEditorAttribute", GetEditorAttribute),
44 DECLARE_NAPI_FUNCTION("getTextIndexAtCursor", GetTextIndexAtCursor),
45 DECLARE_NAPI_FUNCTION("moveCursor", MoveCursor),
46 DECLARE_NAPI_FUNCTION("selectByRange", SelectByRange),
47 DECLARE_NAPI_FUNCTION("selectByMovement", SelectByMovement),
48 DECLARE_NAPI_FUNCTION("sendExtendAction", SendExtendAction),
49 DECLARE_NAPI_FUNCTION("insertTextSync", InsertTextSync),
50 DECLARE_NAPI_FUNCTION("moveCursorSync", MoveCursorSync),
51 DECLARE_NAPI_FUNCTION("getEditorAttributeSync", GetEditorAttributeSync),
52 DECLARE_NAPI_FUNCTION("selectByRangeSync", SelectByRangeSync),
53 DECLARE_NAPI_FUNCTION("selectByMovementSync", SelectByMovementSync),
54 DECLARE_NAPI_FUNCTION("getTextIndexAtCursorSync", GetTextIndexAtCursorSync),
55 DECLARE_NAPI_FUNCTION("deleteForwardSync", DeleteForwardSync),
56 DECLARE_NAPI_FUNCTION("deleteBackwardSync", DeleteBackwardSync),
57 DECLARE_NAPI_FUNCTION("getForwardSync", GetForwardSync),
58 DECLARE_NAPI_FUNCTION("getBackwardSync", GetBackwardSync)
59 };
60 napi_value cons = nullptr;
61 NAPI_CALL(env, napi_define_class(env, TIC_CLASS_NAME.c_str(), TIC_CLASS_NAME.size(), JsConstructor, nullptr,
62 sizeof(properties) / sizeof(napi_property_descriptor), properties, &cons));
63 NAPI_CALL(env, napi_create_reference(env, cons, 1, &TICRef_));
64 NAPI_CALL(env, napi_set_named_property(env, info, TIC_CLASS_NAME.c_str(), cons));
65
66 return info;
67 }
68
MoveCursor(napi_env env,napi_callback_info info)69 napi_value JsTextInputClientEngine::MoveCursor(napi_env env, napi_callback_info info)
70 {
71 auto ctxt = std::make_shared<MoveCursorContext>();
72 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
73 PARAM_CHECK_RETURN(env, argc > 0, " should 1 or 2 parameters! ", TYPE_NONE, napi_generic_failure);
74 auto status = JsUtils::GetValue(env, argv[0], ctxt->num);
75 if (status == napi_ok) {
76 ctxt->info = { std::chrono::system_clock::now(), EditorEvent::MOVE_CURSOR };
77 editorQueue_.Push(ctxt->info);
78 }
79 return status;
80 };
81 auto exec = [ctxt](AsyncCall::Context *ctx) {
82 editorQueue_.Wait(ctxt->info);
83 int32_t code = InputMethodAbility::GetInstance()->MoveCursor(ctxt->num);
84 editorQueue_.Pop();
85 if (code == ErrorCode::NO_ERROR) {
86 ctxt->status = napi_ok;
87 ctxt->SetState(ctxt->status);
88 } else {
89 ctxt->SetErrorCode(code);
90 }
91 };
92 ctxt->SetAction(std::move(input));
93 // 2 means JsAPI:moveCursor has 2 params at most.
94 AsyncCall asyncCall(env, info, ctxt, 2);
95 return asyncCall.Call(env, exec, "moveCursor");
96 }
97
MoveCursorSync(napi_env env,napi_callback_info info)98 napi_value JsTextInputClientEngine::MoveCursorSync(napi_env env, napi_callback_info info)
99 {
100 EditorEventInfo eventInfo = { std::chrono::system_clock::now(), EditorEvent::MOVE_CURSOR};
101 editorQueue_.Push(eventInfo);
102 editorQueue_.Wait(eventInfo);
103 size_t argc = 1;
104 napi_value argv[1] = { nullptr };
105 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
106 int32_t direction = 0;
107 // 1 means least param num.
108 if (argc < 1 || JsUtil::GetType(env, argv[0]) != napi_number || !JsUtil::GetValue(env, argv[0], direction)
109 || direction < 0) {
110 editorQueue_.Pop();
111 JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_PARAMCHECK, "please check the params", TYPE_NONE);
112 return JsUtil::Const::Null(env);
113 }
114 IMSA_HILOGD("moveCursor , direction: %{public}d", direction);
115 int32_t ret = InputMethodAbility::GetInstance()->MoveCursor(direction);
116 editorQueue_.Pop();
117 if (ret != ErrorCode::NO_ERROR) {
118 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to move cursor", TYPE_NONE);
119 }
120 return JsUtil::Const::Null(env);
121 }
122
JsConstructor(napi_env env,napi_callback_info cbinfo)123 napi_value JsTextInputClientEngine::JsConstructor(napi_env env, napi_callback_info cbinfo)
124 {
125 napi_value thisVar = nullptr;
126 NAPI_CALL(env, napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, nullptr));
127
128 JsTextInputClientEngine *clientObject = new (std::nothrow) JsTextInputClientEngine();
129 if (clientObject == nullptr) {
130 IMSA_HILOGE("clientObject is nullptr");
131 napi_value result = nullptr;
132 napi_get_null(env, &result);
133 return result;
134 }
135 auto finalize = [](napi_env env, void *data, void *hint) {
136 IMSA_HILOGD("JsTextInputClientEngine finalize");
137 auto *objInfo = reinterpret_cast<JsTextInputClientEngine *>(data);
138 if (objInfo != nullptr) {
139 delete objInfo;
140 }
141 };
142 napi_status status = napi_wrap(env, thisVar, clientObject, finalize, nullptr, nullptr);
143 if (status != napi_ok) {
144 IMSA_HILOGE("JsTextInputClientEngine napi_wrap failed: %{public}d", status);
145 delete clientObject;
146 return nullptr;
147 }
148 return thisVar;
149 }
150
GetTextInputClientInstance(napi_env env)151 napi_value JsTextInputClientEngine::GetTextInputClientInstance(napi_env env)
152 {
153 napi_value instance = nullptr;
154 napi_value cons = nullptr;
155 if (napi_get_reference_value(env, TICRef_, &cons) != napi_ok) {
156 IMSA_HILOGE("JsTextInputClientEngine::napi_get_reference_value not ok");
157 return nullptr;
158 }
159 if (napi_new_instance(env, cons, 0, nullptr, &instance) != napi_ok) {
160 IMSA_HILOGE("JsTextInputClientEngine::napi_new_instance not ok");
161 return nullptr;
162 }
163 return instance;
164 }
165
GetResult(napi_env env,std::string & text)166 napi_value JsTextInputClientEngine::GetResult(napi_env env, std::string &text)
167 {
168 napi_value jsText = nullptr;
169 napi_create_string_utf8(env, text.c_str(), NAPI_AUTO_LENGTH, &jsText);
170 return jsText;
171 }
172
GetResultEditorAttribute(napi_env env,std::shared_ptr<GetEditorAttributeContext> getEditorAttribute)173 napi_value JsTextInputClientEngine::GetResultEditorAttribute(
174 napi_env env, std::shared_ptr<GetEditorAttributeContext> getEditorAttribute)
175 {
176 napi_value editorAttribute = nullptr;
177 napi_create_object(env, &editorAttribute);
178
179 napi_value jsValue = nullptr;
180 napi_create_int32(env, getEditorAttribute->enterKeyType, &jsValue);
181 napi_set_named_property(env, editorAttribute, "enterKeyType", jsValue);
182
183 napi_value jsMethodId = nullptr;
184 napi_create_int32(env, getEditorAttribute->inputPattern, &jsMethodId);
185 napi_set_named_property(env, editorAttribute, "inputPattern", jsMethodId);
186
187 return editorAttribute;
188 }
189
GetSelectRange(napi_env env,napi_value argv,std::shared_ptr<SelectContext> ctxt)190 napi_status JsTextInputClientEngine::GetSelectRange(napi_env env, napi_value argv, std::shared_ptr<SelectContext> ctxt)
191 {
192 napi_status status = napi_generic_failure;
193 napi_value napiValue = nullptr;
194 status = napi_get_named_property(env, argv, "start", &napiValue);
195 PARAM_CHECK_RETURN(env, status == napi_ok, "missing start parameter.", TYPE_NONE, status);
196 status = JsUtils::GetValue(env, napiValue, ctxt->start);
197 CHECK_RETURN(status == napi_ok, "failed to get start value", status);
198
199 status = napi_get_named_property(env, argv, "end", &napiValue);
200 PARAM_CHECK_RETURN(env, status == napi_ok, "missing end parameter.", TYPE_NONE, status);
201 status = JsUtils::GetValue(env, napiValue, ctxt->end);
202 if (status != napi_ok) {
203 IMSA_HILOGE("failed to get end value");
204 }
205 return status;
206 }
207
GetSelectMovement(napi_env env,napi_value argv,std::shared_ptr<SelectContext> ctxt)208 napi_status JsTextInputClientEngine::GetSelectMovement(
209 napi_env env, napi_value argv, std::shared_ptr<SelectContext> ctxt)
210 {
211 napi_status status = napi_generic_failure;
212 napi_value napiValue = nullptr;
213 status = napi_get_named_property(env, argv, "direction", &napiValue);
214 PARAM_CHECK_RETURN(env, status == napi_ok, "missing direction parameter.", TYPE_NONE, status);
215 status = JsUtils::GetValue(env, napiValue, ctxt->direction);
216 if (status != napi_ok) {
217 IMSA_HILOGE("failed to get direction value");
218 }
219 return status;
220 }
221
SendKeyFunction(napi_env env,napi_callback_info info)222 napi_value JsTextInputClientEngine::SendKeyFunction(napi_env env, napi_callback_info info)
223 {
224 auto ctxt = std::make_shared<SendKeyFunctionContext>();
225 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
226 PARAM_CHECK_RETURN(env, argc > 0, "should 1 or 2 parameters!", TYPE_NONE, napi_generic_failure);
227 return JsUtils::GetValue(env, argv[0], ctxt->action);
228 };
229 auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
230 napi_status status = napi_get_boolean(env, ctxt->isSendKeyFunction, result);
231 return status;
232 };
233 auto exec = [ctxt](AsyncCall::Context *ctx) {
234 int32_t code = InputMethodAbility::GetInstance()->SendFunctionKey(ctxt->action);
235 if (code == ErrorCode::NO_ERROR) {
236 ctxt->status = napi_ok;
237 ctxt->SetState(ctxt->status);
238 ctxt->isSendKeyFunction = true;
239 } else {
240 ctxt->SetErrorCode(code);
241 }
242 };
243 ctxt->SetAction(std::move(input), std::move(output));
244 // 2 means JsAPI:sendKeyFunction has 2 params at most.
245 AsyncCall asyncCall(env, info, ctxt, 2);
246 return asyncCall.Call(env, exec, "sendKeyFunction");
247 }
248
DeleteForwardSync(napi_env env,napi_callback_info info)249 napi_value JsTextInputClientEngine::DeleteForwardSync(napi_env env, napi_callback_info info)
250 {
251 InputMethodSyncTrace tracer("JS_DeleteForwardSync");
252 EditorEventInfo eventInfo = { std::chrono::system_clock::now(), EditorEvent::DELETE_FORWARD };
253 editorQueue_.Push(eventInfo);
254 int64_t start = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
255 editorQueue_.Wait(eventInfo);
256 PrintEditorQueueInfoIfTimeout(start, eventInfo);
257 size_t argc = 1;
258 napi_value argv[1] = { nullptr };
259 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
260 int32_t length = 0;
261 // 1 means least param num.
262 if (argc < 1 || JsUtil::GetType(env, argv[0]) != napi_number || !JsUtil::GetValue(env, argv[0], length)
263 || length < 0) {
264 JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_PARAMCHECK, "please check the params", TYPE_NONE);
265 editorQueue_.Pop();
266 return JsUtil::Const::Null(env);
267 }
268 IMSA_HILOGD("Delete forward, length: %{public}d", length);
269 int32_t ret = InputMethodAbility::GetInstance()->DeleteForward(length);
270 editorQueue_.Pop();
271 if (ret != ErrorCode::NO_ERROR) {
272 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to delete forward", TYPE_NONE);
273 }
274 return JsUtil::Const::Null(env);
275 }
276
DeleteForward(napi_env env,napi_callback_info info)277 napi_value JsTextInputClientEngine::DeleteForward(napi_env env, napi_callback_info info)
278 {
279 InputMethodSyncTrace tracer("JS_DeleteForward");
280 auto ctxt = std::make_shared<DeleteForwardContext>();
281 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
282 PARAM_CHECK_RETURN(env, argc > 0, "should 1 or 2 parameters!", TYPE_NONE, napi_generic_failure);
283 auto status = JsUtils::GetValue(env, argv[0], ctxt->length);
284 if (status == napi_ok) {
285 ctxt->info = { std::chrono::system_clock::now(), EditorEvent::DELETE_FORWARD };
286 editorQueue_.Push(ctxt->info);
287 }
288 return status;
289 };
290 auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
291 napi_status status = napi_get_boolean(env, ctxt->isDeleteForward, result);
292 return status;
293 };
294 auto exec = [ctxt](AsyncCall::Context *ctx) {
295 InputMethodSyncTrace tracer("JS_DeleteForward_Exec");
296 int64_t start = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
297 editorQueue_.Wait(ctxt->info);
298 PrintEditorQueueInfoIfTimeout(start, ctxt->info);
299 int32_t code = InputMethodAbility::GetInstance()->DeleteForward(ctxt->length);
300 editorQueue_.Pop();
301 if (code == ErrorCode::NO_ERROR) {
302 ctxt->status = napi_ok;
303 ctxt->SetState(ctxt->status);
304 ctxt->isDeleteForward = true;
305 } else {
306 ctxt->SetErrorCode(code);
307 }
308 };
309 ctxt->SetAction(std::move(input), std::move(output));
310 // 2 means JsAPI:deleteForward has 2 params at most.
311 AsyncCall asyncCall(env, info, ctxt, 2);
312 return asyncCall.Call(env, exec, "deleteForward");
313 }
314
DeleteBackwardSync(napi_env env,napi_callback_info info)315 napi_value JsTextInputClientEngine::DeleteBackwardSync(napi_env env, napi_callback_info info)
316 {
317 EditorEventInfo eventInfo = { std::chrono::system_clock::now(), EditorEvent::DELETE_BACKWARD };
318 editorQueue_.Push(eventInfo);
319 editorQueue_.Wait(eventInfo);
320 size_t argc = 1;
321 napi_value argv[1] = { nullptr };
322 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
323 int32_t length = 0;
324 // 1 means least param num.
325 if (argc < 1 || JsUtil::GetType(env, argv[0]) != napi_number || !JsUtil::GetValue(env, argv[0], length)
326 || length < 0) {
327 editorQueue_.Pop();
328 JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_PARAMCHECK, "please check the params", TYPE_NONE);
329 return JsUtil::Const::Null(env);
330 }
331 IMSA_HILOGD("Delete backward, length: %{public}d", length);
332 int32_t ret = InputMethodAbility::GetInstance()->DeleteBackward(length);
333 editorQueue_.Pop();
334 if (ret != ErrorCode::NO_ERROR) {
335 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to delete backward", TYPE_NONE);
336 }
337 return JsUtil::Const::Null(env);
338 }
339
DeleteBackward(napi_env env,napi_callback_info info)340 napi_value JsTextInputClientEngine::DeleteBackward(napi_env env, napi_callback_info info)
341 {
342 auto ctxt = std::make_shared<DeleteBackwardContext>();
343 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
344 PARAM_CHECK_RETURN(env, argc > 0, "should 1 or 2 parameters!", TYPE_NONE, napi_generic_failure);
345 auto status = JsUtils::GetValue(env, argv[0], ctxt->length);
346 if (status == napi_ok) {
347 ctxt->info = { std::chrono::system_clock::now(), EditorEvent::DELETE_BACKWARD };
348 editorQueue_.Push(ctxt->info);
349 }
350 return status;
351 };
352 auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
353 napi_status status = napi_get_boolean(env, ctxt->isDeleteBackward, result);
354 return status;
355 };
356 auto exec = [ctxt](AsyncCall::Context *ctx) {
357 editorQueue_.Wait(ctxt->info);
358 int32_t code = InputMethodAbility::GetInstance()->DeleteBackward(ctxt->length);
359 editorQueue_.Pop();
360 if (code == ErrorCode::NO_ERROR) {
361 ctxt->status = napi_ok;
362 ctxt->SetState(ctxt->status);
363 ctxt->isDeleteBackward = true;
364 } else {
365 ctxt->SetErrorCode(code);
366 }
367 };
368 ctxt->SetAction(std::move(input), std::move(output));
369 // 2 means JsAPI:deleteBackward has 2 params at most.
370 AsyncCall asyncCall(env, info, ctxt, 2);
371 return asyncCall.Call(env, exec, "deleteBackward");
372 }
373
InsertText(napi_env env,napi_callback_info info)374 napi_value JsTextInputClientEngine::InsertText(napi_env env, napi_callback_info info)
375 {
376 InputMethodSyncTrace tracer("JS_InsertText");
377 auto ctxt = std::make_shared<InsertTextContext>();
378 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
379 PARAM_CHECK_RETURN(env, argc > 0, "should 1 or 2 parameters!", TYPE_NONE, napi_generic_failure);
380 auto status = JsUtils::GetValue(env, argv[0], ctxt->text);
381 if (status == napi_ok) {
382 ctxt->info = { std::chrono::system_clock::now(), EditorEvent::INSERT_TEXT };
383 editorQueue_.Push(ctxt->info);
384 }
385 return status;
386 };
387 auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
388 napi_status status = napi_get_boolean(env, ctxt->isInsertText, result);
389 return status;
390 };
391 auto exec = [ctxt](AsyncCall::Context *ctx) {
392 InputMethodSyncTrace tracer("JS_InsertText_Exec");
393 int64_t start = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
394 editorQueue_.Wait(ctxt->info);
395 PrintEditorQueueInfoIfTimeout(start, ctxt->info);
396 int32_t code = InputMethodAbility::GetInstance()->InsertText(ctxt->text);
397 editorQueue_.Pop();
398 if (code == ErrorCode::NO_ERROR) {
399 ctxt->status = napi_ok;
400 ctxt->SetState(ctxt->status);
401 ctxt->isInsertText = true;
402 } else {
403 ctxt->SetErrorCode(code);
404 }
405 };
406 ctxt->SetAction(std::move(input), std::move(output));
407 // 2 means JsAPI:insertText has 2 params at most.
408 AsyncCall asyncCall(env, info, ctxt, 2);
409 return asyncCall.Call(env, exec, "insertText");
410 }
411
InsertTextSync(napi_env env,napi_callback_info info)412 napi_value JsTextInputClientEngine::InsertTextSync(napi_env env, napi_callback_info info)
413 {
414 InputMethodSyncTrace tracer("JS_InsertTextSync");
415 EditorEventInfo eventInfo = { std::chrono::system_clock::now(), EditorEvent::INSERT_TEXT};
416 editorQueue_.Push(eventInfo);
417 int64_t start = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
418 editorQueue_.Wait(eventInfo);
419 PrintEditorQueueInfoIfTimeout(start, eventInfo);
420 size_t argc = 1;
421 napi_value argv[1] = { nullptr };
422 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
423 std::string text;
424 // 1 means least param num.
425 if (argc < 1 || JsUtil::GetType(env, argv[0]) != napi_string || !JsUtil::GetValue(env, argv[0], text)) {
426 editorQueue_.Pop();
427 JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_PARAMCHECK, "please check the params", TYPE_NONE);
428 return JsUtil::Const::Null(env);
429 }
430 IMSA_HILOGD("insert text , text: %{public}s", text.c_str());
431 int32_t ret = InputMethodAbility::GetInstance()->InsertText(text);
432 editorQueue_.Pop();
433 if (ret != ErrorCode::NO_ERROR) {
434 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to insert text", TYPE_NONE);
435 }
436 return JsUtil::Const::Null(env);
437 }
438
GetForwardSync(napi_env env,napi_callback_info info)439 napi_value JsTextInputClientEngine::GetForwardSync(napi_env env, napi_callback_info info)
440 {
441 InputMethodSyncTrace tracer("JS_GetForwardSync");
442 EditorEventInfo eventInfo = { std::chrono::system_clock::now(), EditorEvent::GET_FORWARD };
443 editorQueue_.Push(eventInfo);
444 int64_t start = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
445 editorQueue_.Wait(eventInfo);
446 PrintEditorQueueInfoIfTimeout(start, eventInfo);
447 size_t argc = 1;
448 napi_value argv[1] = { nullptr };
449 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
450 int32_t length = 0;
451 // 1 means least param num.
452 if (argc < 1 || JsUtil::GetType(env, argv[0]) != napi_number || !JsUtil::GetValue(env, argv[0], length)
453 || length < 0) {
454 editorQueue_.Pop();
455 JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_PARAMCHECK, "please check the params", TYPE_NONE);
456 return JsUtil::Const::Null(env);
457 }
458 IMSA_HILOGD("Get forward, length: %{public}d", length);
459 std::u16string text;
460 int32_t ret = InputMethodAbility::GetInstance()->GetTextBeforeCursor(length, text);
461 editorQueue_.Pop();
462 if (ret != ErrorCode::NO_ERROR) {
463 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to get forward", TYPE_NONE);
464 return JsUtil::Const::Null(env);
465 }
466 napi_value result = nullptr;
467 auto status = JsUtils::GetValue(env, Str16ToStr8(text), result);
468 CHECK_RETURN(status == napi_ok, "GetValue failed", JsUtil::Const::Null(env));
469 return result;
470 }
471
GetForward(napi_env env,napi_callback_info info)472 napi_value JsTextInputClientEngine::GetForward(napi_env env, napi_callback_info info)
473 {
474 InputMethodSyncTrace tracer("JS_GetForward");
475 auto ctxt = std::make_shared<GetForwardContext>();
476 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
477 PARAM_CHECK_RETURN(env, argc > 0, "should 1 or 2 parameters!", TYPE_NONE, napi_generic_failure);
478 auto status = JsUtils::GetValue(env, argv[0], ctxt->length);
479 if (status == napi_ok) {
480 ctxt->info = { std::chrono::system_clock::now(), EditorEvent::GET_FORWARD };
481 editorQueue_.Push(ctxt->info);
482 }
483 return status;
484 };
485 auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
486 napi_value data = GetResult(env, ctxt->text);
487 *result = data;
488 return napi_ok;
489 };
490 auto exec = [ctxt](AsyncCall::Context *ctx) {
491 InputMethodSyncTrace tracer("JS_GetForward_Exec");
492 int64_t start = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
493 editorQueue_.Wait(ctxt->info);
494 PrintEditorQueueInfoIfTimeout(start, ctxt->info);
495 std::u16string temp;
496 int32_t code = InputMethodAbility::GetInstance()->GetTextBeforeCursor(ctxt->length, temp);
497 editorQueue_.Pop();
498 if (code == ErrorCode::NO_ERROR) {
499 ctxt->status = napi_ok;
500 ctxt->SetState(ctxt->status);
501 ctxt->text = Str16ToStr8(temp);
502 } else {
503 ctxt->SetErrorCode(code);
504 }
505 };
506 ctxt->SetAction(std::move(input), std::move(output));
507 // 2 means JsAPI:getForward has 2 params at most.
508 AsyncCall asyncCall(env, info, ctxt, 2);
509 return asyncCall.Call(env, exec, "getForward");
510 }
511
GetBackwardSync(napi_env env,napi_callback_info info)512 napi_value JsTextInputClientEngine::GetBackwardSync(napi_env env, napi_callback_info info)
513 {
514 EditorEventInfo eventInfo = { std::chrono::system_clock::now(), EditorEvent::GET_BACKWARD };
515 editorQueue_.Push(eventInfo);
516 editorQueue_.Wait(eventInfo);
517 size_t argc = 1;
518 napi_value argv[1] = { nullptr };
519 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
520 int32_t length = 0;
521 // 1 means least param num.
522 if (argc < 1 || JsUtil::GetType(env, argv[0]) != napi_number || !JsUtil::GetValue(env, argv[0], length)
523 || length < 0) {
524 editorQueue_.Pop();
525 JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_PARAMCHECK, "please check the params", TYPE_NONE);
526 return JsUtil::Const::Null(env);
527 }
528 IMSA_HILOGD("Get backward, length: %{public}d", length);
529 std::u16string text;
530 int32_t ret = InputMethodAbility::GetInstance()->GetTextAfterCursor(length, text);
531 editorQueue_.Pop();
532 if (ret != ErrorCode::NO_ERROR) {
533 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to get backward", TYPE_NONE);
534 return JsUtil::Const::Null(env);
535 }
536 napi_value result = nullptr;
537 auto status = JsUtils::GetValue(env, Str16ToStr8(text), result);
538 CHECK_RETURN(status == napi_ok, "GetValue failed", JsUtil::Const::Null(env));
539 return result;
540 }
541
GetBackward(napi_env env,napi_callback_info info)542 napi_value JsTextInputClientEngine::GetBackward(napi_env env, napi_callback_info info)
543 {
544 auto ctxt = std::make_shared<GetBackwardContext>();
545 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
546 PARAM_CHECK_RETURN(env, argc > 0, "should 1 or 2 parameters!", TYPE_NONE, napi_generic_failure);
547 auto status = JsUtils::GetValue(env, argv[0], ctxt->length);
548 if (status == napi_ok) {
549 ctxt->info = { std::chrono::system_clock::now(), EditorEvent::GET_BACKWARD };
550 editorQueue_.Push(ctxt->info);
551 }
552 return status;
553 };
554 auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
555 napi_value data = GetResult(env, ctxt->text);
556 *result = data;
557 return napi_ok;
558 };
559 auto exec = [ctxt](AsyncCall::Context *ctx) {
560 editorQueue_.Wait(ctxt->info);
561 std::u16string temp;
562 int32_t code = InputMethodAbility::GetInstance()->GetTextAfterCursor(ctxt->length, temp);
563 editorQueue_.Pop();
564 if (code == ErrorCode::NO_ERROR) {
565 ctxt->status = napi_ok;
566 ctxt->SetState(ctxt->status);
567 ctxt->text = Str16ToStr8(temp);
568 } else {
569 ctxt->SetErrorCode(code);
570 }
571 };
572 ctxt->SetAction(std::move(input), std::move(output));
573 // 2 means JsAPI:getBackward has 2 params at most.
574 AsyncCall asyncCall(env, info, ctxt, 2);
575 return asyncCall.Call(env, exec, "getBackward");
576 }
577
GetEditorAttributeSync(napi_env env,napi_callback_info info)578 napi_value JsTextInputClientEngine::GetEditorAttributeSync(napi_env env, napi_callback_info info)
579 {
580 int32_t enterKeyType = 0;
581 int32_t ret = InputMethodAbility::GetInstance()->GetEnterKeyType(enterKeyType);
582 if (ret != ErrorCode::NO_ERROR) {
583 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to getEnterKeyType", TYPE_NONE);
584 }
585 IMSA_HILOGD("enterKeyType: %{public}d", enterKeyType);
586
587 int32_t inputPattern = 0;
588 ret = InputMethodAbility::GetInstance()->GetInputPattern(inputPattern);
589 if (ret != ErrorCode::NO_ERROR) {
590 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to getInputPattern", TYPE_NONE);
591 }
592 IMSA_HILOGD("patternCode: %{public}d", inputPattern);
593
594 const InputAttribute attribute = { .inputPattern = inputPattern, .enterKeyType = enterKeyType };
595 return JsUtils::GetValue(env, attribute);
596 }
597
GetEditorAttribute(napi_env env,napi_callback_info info)598 napi_value JsTextInputClientEngine::GetEditorAttribute(napi_env env, napi_callback_info info)
599 {
600 auto ctxt = std::make_shared<GetEditorAttributeContext>();
601 auto input = [ctxt](
602 napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status { return napi_ok; };
603 auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
604 napi_value data = GetResultEditorAttribute(env, ctxt);
605 *result = data;
606 return napi_ok;
607 };
608 auto exec = [ctxt](AsyncCall::Context *ctx) {
609 int32_t typeCode = InputMethodAbility::GetInstance()->GetEnterKeyType(ctxt->enterKeyType);
610 int32_t patternCode = InputMethodAbility::GetInstance()->GetInputPattern(ctxt->inputPattern);
611 if (typeCode == ErrorCode::NO_ERROR && patternCode == ErrorCode::NO_ERROR) {
612 ctxt->status = napi_ok;
613 ctxt->SetState(ctxt->status);
614 } else {
615 typeCode == ErrorCode::NO_ERROR ? ctxt->SetErrorCode(patternCode) : ctxt->SetErrorCode(typeCode);
616 }
617 };
618 ctxt->SetAction(std::move(input), std::move(output));
619 // 1 means JsAPI:getEditorAttribute has 1 params at most.
620 AsyncCall asyncCall(env, info, ctxt, 1);
621 return asyncCall.Call(env, exec, "getEditorAttribute");
622 }
623
SelectByRange(napi_env env,napi_callback_info info)624 napi_value JsTextInputClientEngine::SelectByRange(napi_env env, napi_callback_info info)
625 {
626 IMSA_HILOGD("run in");
627 auto ctxt = std::make_shared<SelectContext>();
628 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
629 PARAM_CHECK_RETURN(env, argc > 0, "should 1 or 2 parameters!", TYPE_NONE, napi_generic_failure);
630 napi_valuetype valueType = napi_undefined;
631 napi_typeof(env, argv[0], &valueType);
632 PARAM_CHECK_RETURN(env, valueType == napi_object, "range", TYPE_OBJECT, napi_generic_failure);
633 auto status = GetSelectRange(env, argv[0], ctxt);
634 if (status == napi_ok) {
635 ctxt->info = { std::chrono::system_clock::now(), EditorEvent::SELECT_BY_RANGE };
636 editorQueue_.Push(ctxt->info);
637 }
638 return status;
639 };
640 auto output = [ctxt](napi_env env, napi_value *result) -> napi_status { return napi_ok; };
641 auto exec = [ctxt](AsyncCall::Context *ctx) {
642 editorQueue_.Wait(ctxt->info);
643 int32_t code = InputMethodAbility::GetInstance()->SelectByRange(ctxt->start, ctxt->end);
644 editorQueue_.Pop();
645 if (code == ErrorCode::NO_ERROR) {
646 ctxt->status = napi_ok;
647 ctxt->SetState(ctxt->status);
648 } else {
649 ctxt->SetErrorCode(code);
650 }
651 };
652 ctxt->SetAction(std::move(input), std::move(output));
653 // 2 means JsAPI:selectByRange has 2 params at most.
654 AsyncCall asyncCall(env, info, ctxt, 2);
655 return asyncCall.Call(env, exec, "selectByRange");
656 }
657
SelectByRangeSync(napi_env env,napi_callback_info info)658 napi_value JsTextInputClientEngine::SelectByRangeSync(napi_env env, napi_callback_info info)
659 {
660 IMSA_HILOGD("SelectByRangeSync");
661 EditorEventInfo eventInfo = { std::chrono::system_clock::now(), EditorEvent::SELECT_BY_RANGE};
662 editorQueue_.Push(eventInfo);
663 editorQueue_.Wait(eventInfo);
664 size_t argc = 1;
665 napi_value argv[1] = { nullptr };
666 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
667 if (argc < 1 || JsUtil::GetType(env, argv[0]) != napi_object) {
668 editorQueue_.Pop();
669 JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_PARAMCHECK, "please check the params", TYPE_NONE);
670 return JsUtil::Const::Null(env);
671 }
672 auto ctxt = std::make_shared<SelectContext>();
673 auto status = GetSelectRange(env, argv[0], ctxt);
674 if (status != napi_ok) {
675 editorQueue_.Pop();
676 JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_PARAMCHECK, "failed to get start or end.", TYPE_NONE);
677 return JsUtil::Const::Null(env);
678 }
679 IMSA_HILOGD("start: %{public}d, end: %{public}d", ctxt->start, ctxt->end);
680 int32_t ret = InputMethodAbility::GetInstance()->SelectByRange(ctxt->start, ctxt->end);
681 editorQueue_.Pop();
682 if (ret != ErrorCode::NO_ERROR) {
683 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to select by range.", TYPE_NONE);
684 }
685 return JsUtil::Const::Null(env);
686 }
687
SelectByMovementSync(napi_env env,napi_callback_info info)688 napi_value JsTextInputClientEngine::SelectByMovementSync(napi_env env, napi_callback_info info)
689 {
690 IMSA_HILOGD("run in");
691 EditorEventInfo eventInfo = { std::chrono::system_clock::now(), EditorEvent::SELECT_BY_MOVEMENT};
692 editorQueue_.Push(eventInfo);
693 editorQueue_.Wait(eventInfo);
694 size_t argc = 1;
695 napi_value argv[1] = { nullptr };
696 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
697 if (argc < 1 || JsUtil::GetType(env, argv[0]) != napi_object) {
698 editorQueue_.Pop();
699 JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_PARAMCHECK, "please check the params", TYPE_NONE);
700 return JsUtil::Const::Null(env);
701 }
702 auto ctxt = std::make_shared<SelectContext>();
703 auto status = GetSelectMovement(env, argv[0], ctxt);
704 if (status != napi_ok) {
705 editorQueue_.Pop();
706 JsUtils::ThrowException(env, IMFErrorCode::EXCEPTION_PARAMCHECK, "failed to get direction.", TYPE_NONE);
707 return JsUtil::Const::Null(env);
708 }
709 IMSA_HILOGD("direction: %{public}d", ctxt->direction);
710 int32_t ret = InputMethodAbility::GetInstance()->SelectByMovement(ctxt->direction);
711 editorQueue_.Pop();
712 if (ret != ErrorCode::NO_ERROR) {
713 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to select by movement.", TYPE_NONE);
714 }
715 return JsUtil::Const::Null(env);
716 }
717
SelectByMovement(napi_env env,napi_callback_info info)718 napi_value JsTextInputClientEngine::SelectByMovement(napi_env env, napi_callback_info info)
719 {
720 IMSA_HILOGD("run in");
721 auto ctxt = std::make_shared<SelectContext>();
722 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
723 PARAM_CHECK_RETURN(env, argc > 0, "should 1 or 2 parameters!", TYPE_NONE, napi_generic_failure);
724 napi_valuetype valueType = napi_undefined;
725 napi_typeof(env, argv[0], &valueType);
726 PARAM_CHECK_RETURN(env, valueType == napi_object, "movement", TYPE_OBJECT, napi_generic_failure);
727 auto status = GetSelectMovement(env, argv[0], ctxt);
728 if (status == napi_ok) {
729 ctxt->info = { std::chrono::system_clock::now(), EditorEvent::SELECT_BY_MOVEMENT };
730 editorQueue_.Push(ctxt->info);
731 }
732 return status;
733 };
734 auto output = [ctxt](napi_env env, napi_value *result) -> napi_status { return napi_ok; };
735 auto exec = [ctxt](AsyncCall::Context *ctx) {
736 editorQueue_.Wait(ctxt->info);
737 int32_t code = InputMethodAbility::GetInstance()->SelectByMovement(ctxt->direction);
738 editorQueue_.Pop();
739 if (code == ErrorCode::NO_ERROR) {
740 ctxt->status = napi_ok;
741 ctxt->SetState(ctxt->status);
742 } else {
743 ctxt->SetErrorCode(code);
744 }
745 };
746 ctxt->SetAction(std::move(input), std::move(output));
747 // 2 means JsAPI:selectByMovement has 2 params at most.
748 AsyncCall asyncCall(env, info, ctxt, 2);
749 return asyncCall.Call(env, exec, "selectByMovement");
750 }
751
SendExtendAction(napi_env env,napi_callback_info info)752 napi_value JsTextInputClientEngine::SendExtendAction(napi_env env, napi_callback_info info)
753 {
754 auto ctxt = std::make_shared<SendExtendActionContext>();
755 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
756 PARAM_CHECK_RETURN(env, argc > 0, "should 1 or 2 parameters!", TYPE_NONE, napi_generic_failure);
757 auto status = JsUtils::GetValue(env, argv[0], ctxt->action);
758 if (status == napi_ok) {
759 ctxt->info = { std::chrono::system_clock::now(), EditorEvent::SEND_EXTEND_ACTION };
760 editorQueue_.Push(ctxt->info);
761 }
762 return status;
763 };
764 auto exec = [ctxt](AsyncCall::Context *ctx) {
765 editorQueue_.Wait(ctxt->info);
766 int32_t code = InputMethodAbility::GetInstance()->SendExtendAction(ctxt->action);
767 editorQueue_.Pop();
768 if (code == ErrorCode::NO_ERROR) {
769 ctxt->SetState(napi_ok);
770 return;
771 }
772 ctxt->SetErrorCode(code);
773 };
774 ctxt->SetAction(std::move(input));
775 // 2 means JsAPI:sendExtendAction has 2 params at most.
776 AsyncCall asyncCall(env, info, ctxt, 2);
777 return asyncCall.Call(env, exec, "sendExtendAction");
778 }
779
GetTextIndexAtCursor(napi_env env,napi_callback_info info)780 napi_value JsTextInputClientEngine::GetTextIndexAtCursor(napi_env env, napi_callback_info info)
781 {
782 IMSA_HILOGD("GetTextIndexAtCursor");
783 auto ctxt = std::make_shared<GetTextIndexAtCursorContext>();
784 auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
785 ctxt->info = { std::chrono::system_clock::now(), EditorEvent::GET_TEXT_INDEX_AT_CURSOR };
786 editorQueue_.Push(ctxt->info);
787 return napi_ok;
788 };
789 auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
790 return napi_create_int32(env, ctxt->index, result);
791 };
792 auto exec = [ctxt](AsyncCall::Context *ctx) {
793 editorQueue_.Wait(ctxt->info);
794 int32_t code = InputMethodAbility::GetInstance()->GetTextIndexAtCursor(ctxt->index);
795 editorQueue_.Pop();
796 if (code == ErrorCode::NO_ERROR) {
797 ctxt->status = napi_ok;
798 ctxt->SetState(ctxt->status);
799 } else {
800 ctxt->SetErrorCode(code);
801 }
802 };
803 ctxt->SetAction(std::move(input), std::move(output));
804 // 1 means JsAPI:getTextIndexAtCursor has 1 params at most.
805 AsyncCall asyncCall(env, info, ctxt, 1);
806 return asyncCall.Call(env, exec, "getTextIndexAtCursor");
807 }
808
GetTextIndexAtCursorSync(napi_env env,napi_callback_info info)809 napi_value JsTextInputClientEngine::GetTextIndexAtCursorSync(napi_env env, napi_callback_info info)
810 {
811 IMSA_HILOGD("run in");
812 EditorEventInfo eventInfo = { std::chrono::system_clock::now(), EditorEvent::GET_TEXT_INDEX_AT_CURSOR};
813 editorQueue_.Push(eventInfo);
814 editorQueue_.Wait(eventInfo);
815 int32_t index = 0;
816 int32_t ret = InputMethodAbility::GetInstance()->GetTextIndexAtCursor(index);
817 editorQueue_.Pop();
818 if (ret != ErrorCode::NO_ERROR) {
819 JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to get text index at cursor.", TYPE_NONE);
820 }
821 return JsUtil::GetValue(env, index);
822 }
823
PrintEditorQueueInfoIfTimeout(int64_t start,const EditorEventInfo & currentInfo)824 void JsTextInputClientEngine::PrintEditorQueueInfoIfTimeout(int64_t start, const EditorEventInfo ¤tInfo)
825 {
826 int64_t end = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
827 if (end - start >= MAX_WAIT_TIME) {
828 EditorEventInfo frontInfo;
829 auto ret = editorQueue_.GetFront(frontInfo);
830 int64_t frontTime = duration_cast<microseconds>(frontInfo.timestamp.time_since_epoch()).count();
831 int64_t currentTime = duration_cast<microseconds>(currentInfo.timestamp.time_since_epoch()).count();
832 IMSA_HILOGW("ret:%{public}d,front[%{public}" PRId64 ",%{public}d],current[%{public}" PRId64 ",%{public}d]", ret,
833 frontTime, static_cast<int32_t>(frontInfo.event), currentTime, static_cast<int32_t>(currentInfo.event));
834 }
835 }
836 } // namespace MiscServices
837 } // namespace OHOS
838