• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 &currentInfo)
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