1/*
2 * Copyright (c) 2021-2022 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 <future>
17#include <napi/native_api.h>
18#include <napi/native_node_api.h>
19#include <queue>
20#include <set>
21#include <string>
22#include <unistd.h>
23#include "json.hpp"
24#include "fcntl.h"
25#include "common_utilities_hpp.h"
26#include "frontend_api_defines.h"
27#include "ipc_transactor.h"
28#include "ui_event_observer_napi.h"
29#include "test_server_client.h"
30
31namespace OHOS::uitest {
32    using namespace nlohmann;
33    using namespace std;
34
35    static constexpr size_t NAPI_MAX_BUF_LEN = 1024;
36    static constexpr size_t NAPI_MAX_ARG_COUNT = 8;
37    static constexpr size_t BACKEND_OBJ_GC_BATCH = 100;
38    // type of unexpected or napi-internal error
39    static constexpr napi_status NAPI_ERR = napi_status::napi_generic_failure;
40    // the name of property that represents the objectRef of the backend object
41    static constexpr char PROP_BACKEND_OBJ_REF[] = "backendObjRef";
42    /**For dfx usage, records the uncalled js apis. */
43    static set<string> g_unCalledJsFuncNames;
44    /**For gc usage, records the backend objRefs about to delete. */
45    static queue<string> g_backendObjsAboutToDelete;
46    static mutex g_gcQueueMutex;
47    /**IPC client. */
48    static ApiTransactor g_apiTransactClient(false);
49    static future<void> g_establishConnectionFuture;
50
51    /** Convert js string to cpp string.*/
52    static string JsStrToCppStr(napi_env env, napi_value jsStr)
53    {
54        if (jsStr == nullptr) {
55            return "";
56        }
57        napi_valuetype type;
58        NAPI_CALL_BASE(env, napi_typeof(env, jsStr, &type), "");
59        if (type == napi_undefined || type == napi_null) {
60            return "";
61        }
62        size_t bufSize = 0;
63        char buf[NAPI_MAX_BUF_LEN] = {0};
64        NAPI_CALL_BASE(env, napi_get_value_string_utf8(env, jsStr, buf, NAPI_MAX_BUF_LEN, &bufSize), "");
65        return string(buf, bufSize);
66    }
67
68    /**Lifecycle function, establish connection async, called externally.*/
69    static napi_value ScheduleEstablishConnection(napi_env env, napi_callback_info info)
70    {
71        size_t argc = 1;
72        napi_value value = nullptr;
73        napi_value argv[1] = {0};
74        NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &value, nullptr));
75        NAPI_ASSERT(env, argc > 0, "Need session token argument!");
76        auto token = JsStrToCppStr(env, argv[0]);
77        g_establishConnectionFuture = async(launch::async, [env, token]() {
78            auto &instance = UiEventObserverNapi::Get();
79            using namespace std::placeholders;
80            auto callbackHandler = std::bind(&UiEventObserverNapi::HandleEventCallback, &instance, env, _1, _2);
81            auto result = g_apiTransactClient.InitAndConnectPeer(token, callbackHandler);
82            LOG_I("End setup transaction connection, result=%{public}d", result);
83        });
84        return nullptr;
85    }
86
87    /**Wait connection result sync if need.*/
88    static void WaitForConnectionIfNeed()
89    {
90        if (g_establishConnectionFuture.valid()) {
91            LOG_I("Begin WaitForConnection");
92            g_establishConnectionFuture.get();
93        }
94    }
95
96    /**Encapsulates the data objects needed in once api transaction.*/
97    struct TransactionContext {
98        napi_value jsThis_ = nullptr;
99        napi_value *jsArgs_ = nullptr;
100        ApiCallInfo callInfo_;
101    };
102
103    static napi_value CreateJsException(napi_env env, uint32_t code, string_view msg)
104    {
105        napi_value codeValue;
106        napi_value msgValue;
107        napi_value errorValue;
108        napi_create_uint32(env, code, &codeValue);
109        napi_create_string_utf8(env, msg.data(), NAPI_AUTO_LENGTH, &msgValue);
110        napi_create_error(env, nullptr, msgValue, &errorValue);
111        napi_set_named_property(env, errorValue, "code", codeValue);
112        return errorValue;
113    }
114
115    /**Set object constructor function to global as attribute.*/
116    static napi_status MountJsConstructorToGlobal(napi_env env, string_view typeName, napi_value function)
117    {
118        NAPI_ASSERT_BASE(env, function != nullptr, "Null constructor function", napi_invalid_arg);
119        const string name = "constructor_" + string(typeName);
120        napi_value global = nullptr;
121        NAPI_CALL_BASE(env, napi_get_global(env, &global), NAPI_ERR);
122        NAPI_CALL_BASE(env, napi_set_named_property(env, global, name.c_str(), function), NAPI_ERR);
123        return napi_ok;
124    }
125
126    /**Get object constructor function from global as attribute.*/
127    static napi_status GetJsConstructorFromGlobal(napi_env env, string_view typeName, napi_value *pFunction)
128    {
129        NAPI_ASSERT_BASE(env, pFunction != nullptr, "Null constructor receiver", napi_invalid_arg);
130        const string name = "constructor_" + string(typeName);
131        napi_value global = nullptr;
132        NAPI_CALL_BASE(env, napi_get_global(env, &global), NAPI_ERR);
133        NAPI_CALL_BASE(env, napi_get_named_property(env, global, name.c_str(), pFunction), NAPI_ERR);
134        return napi_ok;
135    }
136
137    /**Conversion between value and string, using the builtin JSON methods.*/
138    static napi_status ValueStringConvert(napi_env env, napi_value in, napi_value *out, bool stringify)
139    {
140        if (in == nullptr || out == nullptr) {
141            return napi_invalid_arg;
142        }
143        napi_value global = nullptr;
144        napi_value jsonProp = nullptr;
145        napi_value jsonFunc = nullptr;
146        NAPI_CALL_BASE(env, napi_get_global(env, &global), NAPI_ERR);
147        NAPI_CALL_BASE(env, napi_get_named_property(env, global, "JSON", &jsonProp), NAPI_ERR);
148        if (stringify) {
149            NAPI_CALL_BASE(env, napi_get_named_property(env, jsonProp, "stringify", &jsonFunc), NAPI_ERR);
150        } else {
151            NAPI_CALL_BASE(env, napi_get_named_property(env, jsonProp, "parse", &jsonFunc), NAPI_ERR);
152        }
153        napi_value argv[1] = {in};
154        NAPI_CALL_BASE(env, napi_call_function(env, jsonProp, jsonFunc, 1, argv, out), NAPI_ERR);
155        return napi_ok;
156    }
157
158    /**Unmarshal object from json, throw error and return false if the object cannot be deserialized.*/
159    static napi_status UnmarshalObject(napi_env env, const json &in, napi_value *pOut, napi_value jsThis)
160    {
161        NAPI_ASSERT_BASE(env, pOut != nullptr, "Illegal arguments", napi_invalid_arg);
162        const auto type = in.type();
163        if (type == nlohmann::detail::value_t::null) { // return null
164            NAPI_CALL_BASE(env, napi_get_null(env, pOut), NAPI_ERR);
165            return napi_ok;
166        }
167        if (type != nlohmann::detail::value_t::string) { // non-string value, convert and return object
168            NAPI_CALL_BASE(env, napi_create_string_utf8(env, in.dump().c_str(), NAPI_AUTO_LENGTH, pOut), NAPI_ERR);
169            NAPI_CALL_BASE(env, ValueStringConvert(env, *pOut, pOut, false), NAPI_ERR);
170            return napi_ok;
171        }
172        const auto cppString = in.get<string>();
173        string frontendTypeName;
174        bool bindJsThis = false;
175        for (const auto &classDef : FRONTEND_CLASS_DEFS) {
176            const auto objRefFormat = string(classDef->name_) + "#";
177            if (cppString.find(objRefFormat) == 0) {
178                frontendTypeName = string(classDef->name_);
179                bindJsThis = classDef->bindUiDriver_;
180                break;
181            }
182        }
183        NAPI_CALL_BASE(env, napi_create_string_utf8(env, cppString.c_str(), NAPI_AUTO_LENGTH, pOut), NAPI_ERR);
184        if (frontendTypeName.empty()) { // plain string, return it
185            return napi_ok;
186        }
187        LOG_D("Convert to frontend object: '%{public}s'", frontendTypeName.c_str());
188        // covert to wrapper object and bind the backend objectRef
189        napi_value refValue = *pOut;
190        napi_value constructor = nullptr;
191        NAPI_CALL_BASE(env, GetJsConstructorFromGlobal(env, frontendTypeName, &constructor), NAPI_ERR);
192        NAPI_CALL_BASE(env, napi_new_instance(env, constructor, 1, &refValue, pOut), NAPI_ERR);
193        NAPI_CALL_BASE(env, napi_set_named_property(env, *pOut, PROP_BACKEND_OBJ_REF, refValue), NAPI_ERR);
194        if (bindJsThis) { // bind the jsThis object
195            LOG_D("Bind jsThis");
196            NAPI_ASSERT_BASE(env, jsThis != nullptr, "null jsThis", NAPI_ERR);
197            NAPI_CALL_BASE(env, napi_set_named_property(env, *pOut, "boundObject", jsThis), NAPI_ERR);
198        }
199        return napi_ok;
200    }
201
202    /**Evaluate and convert transaction reply to object. Return the exception raised during the
203     * transaction if any, else return the result object. */
204    static napi_value UnmarshalReply(napi_env env, const TransactionContext &ctx, const ApiReplyInfo &reply)
205    {
206        if (ctx.callInfo_.fdParamIndex_ >= 0) {
207            auto fd = ctx.callInfo_.paramList_.at(INDEX_ZERO).get<int>();
208            (void) close(fd);
209        }
210        LOG_I("Start to Unmarshal transaction result");
211        const auto &message = reply.exception_.message_;
212        ErrCode code = reply.exception_.code_;
213        if (code == INTERNAL_ERROR || code == ERR_INTERNAL) {
214            LOG_E("ErrorInfo: code='%{public}u', message='%{public}s'", code, message.c_str());
215        } else if (reply.exception_.code_ != NO_ERROR) {
216            LOG_I("ErrorInfo: code='%{public}u', message='%{public}s'", code, message.c_str());
217            return CreateJsException(env, code, message);
218        }
219        LOG_I("Start to Unmarshal return value: %{public}s", reply.resultValue_.dump().c_str());
220        const auto resultType = reply.resultValue_.type();
221        napi_value result = nullptr;
222        if (resultType == nlohmann::detail::value_t::null) { // return null
223            NAPI_CALL(env, napi_get_null(env, &result));
224        } else if (resultType == nlohmann::detail::value_t::array) { // return array
225            NAPI_CALL(env, napi_create_array_with_length(env, reply.resultValue_.size(), &result));
226            for (size_t idx = 0; idx < reply.resultValue_.size(); idx++) {
227                napi_value item = nullptr;
228                NAPI_CALL(env, UnmarshalObject(env, reply.resultValue_.at(idx), &item, ctx.jsThis_));
229                NAPI_CALL(env, napi_set_element(env, result, idx, item));
230            }
231        } else { // return single value
232            NAPI_CALL(env, UnmarshalObject(env, reply.resultValue_, &result, ctx.jsThis_));
233        }
234        return result;
235    }
236
237    /**Call api with parameters out, wait for and return result value or throw raised exception.*/
238    napi_value TransactSync(napi_env env, TransactionContext &ctx)
239    {
240        WaitForConnectionIfNeed();
241        LOG_D("TargetApi=%{public}s", ctx.callInfo_.apiId_.data());
242        auto reply = ApiReplyInfo();
243        g_apiTransactClient.Transact(ctx.callInfo_, reply);
244        auto resultValue = UnmarshalReply(env, ctx, reply);
245        auto isError = false;
246        NAPI_CALL(env, napi_is_error(env, resultValue, &isError));
247        if (isError) {
248            NAPI_CALL(env, napi_throw(env, resultValue));
249            NAPI_CALL(env, napi_get_undefined(env, &resultValue)); // return undefined it's error
250        }
251        // notify backend objects deleting
252        if (g_backendObjsAboutToDelete.size() >= BACKEND_OBJ_GC_BATCH) {
253            auto gcCall = ApiCallInfo {.apiId_ = "BackendObjectsCleaner"};
254            unique_lock<mutex> lock(g_gcQueueMutex);
255            for (size_t count = 0; count < BACKEND_OBJ_GC_BATCH; count++) {
256                gcCall.paramList_.emplace_back(g_backendObjsAboutToDelete.front());
257                g_backendObjsAboutToDelete.pop();
258            }
259            lock.unlock();
260            auto gcReply = ApiReplyInfo();
261            g_apiTransactClient.Transact(gcCall, gcReply);
262        }
263        return resultValue;
264    }
265
266    /**Encapsulates the data objects needed for async transaction.*/
267    struct AsyncTransactionCtx {
268        TransactionContext ctx_;
269        ApiReplyInfo reply_;
270        napi_async_work asyncWork_ = nullptr;
271        napi_deferred deferred_ = nullptr;
272        napi_ref jsThisRef_ = nullptr;
273    };
274
275    /**Call api with parameters out, return a promise.*/
276    static napi_value TransactAsync(napi_env env, TransactionContext &ctx)
277    {
278        constexpr uint32_t refCount = 1;
279        LOG_D("TargetApi=%{public}s", ctx.callInfo_.apiId_.data());
280        napi_value resName;
281        NAPI_CALL(env, napi_create_string_latin1(env, __FUNCTION__, NAPI_AUTO_LENGTH, &resName));
282        auto aCtx = new AsyncTransactionCtx();
283        aCtx->ctx_ = ctx;
284        napi_value promise;
285        NAPI_CALL(env, napi_create_promise(env, &(aCtx->deferred_), &promise));
286        NAPI_CALL(env, napi_create_reference(env, ctx.jsThis_, refCount, &(aCtx->jsThisRef_)));
287        napi_create_async_work(
288            env, nullptr, resName,
289            [](napi_env env, void *data) {
290                auto aCtx = reinterpret_cast<AsyncTransactionCtx *>(data);
291                // NOT:: use 'auto&' rather than 'auto', or the result will be set to copy-constructed temp-object
292                auto &ctx = aCtx->ctx_;
293                g_apiTransactClient.Transact(ctx.callInfo_, aCtx->reply_);
294            },
295            [](napi_env env, napi_status status, void *data) {
296                auto aCtx = reinterpret_cast<AsyncTransactionCtx *>(data);
297                napi_handle_scope scope = nullptr;
298                napi_open_handle_scope(env, &scope);
299                if (scope == nullptr) {
300                    return;
301                }
302                napi_get_reference_value(env, aCtx->jsThisRef_, &(aCtx->ctx_.jsThis_));
303                auto resultValue = UnmarshalReply(env, aCtx->ctx_, aCtx->reply_);
304                napi_delete_reference(env, aCtx->jsThisRef_);
305                auto isError = false;
306                napi_is_error(env, resultValue, &isError);
307                if (isError) {
308                    napi_reject_deferred(env, aCtx->deferred_, resultValue);
309                } else {
310                    napi_resolve_deferred(env, aCtx->deferred_, resultValue);
311                }
312                napi_delete_async_work(env, aCtx->asyncWork_);
313                napi_close_handle_scope(env, scope);
314                delete aCtx;
315            },
316            (void *)aCtx, &(aCtx->asyncWork_));
317        napi_queue_async_work(env, aCtx->asyncWork_);
318        return promise;
319    }
320
321    static napi_status GetBackendObjRefProp(napi_env env, napi_value value, napi_value* pOut)
322    {
323        napi_valuetype type = napi_undefined;
324        NAPI_CALL_BASE(env, napi_typeof(env, value, &type), NAPI_ERR);
325        if (type != napi_object) {
326            *pOut = nullptr;
327            return napi_ok;
328        }
329        bool hasRef = false;
330        NAPI_CALL_BASE(env, napi_has_named_property(env, value, PROP_BACKEND_OBJ_REF, &hasRef), NAPI_ERR);
331        if (!hasRef) {
332            *pOut = nullptr;
333        } else {
334            NAPI_CALL_BASE(env, napi_get_named_property(env, value, PROP_BACKEND_OBJ_REF, pOut), NAPI_ERR);
335        }
336        return napi_ok;
337    }
338
339    static void SetPasteBoardData(string_view text)
340    {
341        OHOS::testserver::TestServerClient::GetInstance().SetPasteData(string(text));
342    }
343
344    static void PreprocessTransaction(napi_env env, TransactionContext &ctx, napi_value &error)
345    {
346        auto &paramList = ctx.callInfo_.paramList_;
347        const auto &id = ctx.callInfo_.apiId_;
348        if (id  == "Component.inputText" && paramList.size() > 0) {
349            if (paramList.at(INDEX_ZERO).type() == nlohmann::detail::value_t::string) {
350                SetPasteBoardData(paramList.at(INDEX_ZERO).get<string>());
351            }
352        } else if (id  == "Driver.inputText" && paramList.size() > 1) {
353            if (paramList.at(INDEX_ONE).type() == nlohmann::detail::value_t::string) {
354                SetPasteBoardData(paramList.at(INDEX_ONE).get<string>());
355            }
356        } else if (id  == "Driver.screenCap" || id  == "UiDriver.screenCap" || id  == "Driver.screenCapture") {
357            if (paramList.size() < 1 || paramList.at(0).type() != nlohmann::detail::value_t::string) {
358                LOG_E("Missing file path argument");
359                error = CreateJsException(env, ERR_INVALID_INPUT, "Missing file path argument");
360                return;
361            }
362            auto path = paramList.at(INDEX_ZERO).get<string>();
363            auto fd = open(path.c_str(), O_RDWR | O_CREAT, 0666);
364            if (fd == -1) {
365                LOG_E("Invalid file path: %{public}s", path.data());
366                error = CreateJsException(env, ERR_INVALID_INPUT, "Invalid file path:" + path);
367                return;
368            }
369            paramList[INDEX_ZERO] = fd;
370            ctx.callInfo_.fdParamIndex_ = INDEX_ZERO;
371        } else if (id  == "UIEventObserver.once") {
372            auto err = ApiCallErr(NO_ERROR);
373            UiEventObserverNapi::Get().PreprocessCallOnce(env, ctx.callInfo_, ctx.jsThis_, ctx.jsArgs_, err);
374            if (err.code_ != NO_ERROR) {
375                error = CreateJsException(env, err.code_, err.message_);
376            }
377        }
378    }
379
380    /**Generic js-api callback.*/
381    static napi_value GenericCallback(napi_env env, napi_callback_info info)
382    {
383        // extract callback data
384        TransactionContext ctx;
385        napi_value argv[NAPI_MAX_ARG_COUNT] = {nullptr};
386        auto count = NAPI_MAX_ARG_COUNT;
387        void *pData = nullptr;
388        NAPI_CALL(env, napi_get_cb_info(env, info, &count, argv, &(ctx.jsThis_), &pData));
389        NAPI_ASSERT(env, pData != nullptr, "Null methodDef");
390        ctx.jsArgs_ = argv;
391        auto methodDef = reinterpret_cast<const FrontendMethodDef *>(pData);
392        g_unCalledJsFuncNames.erase(string(methodDef->name_)); // api used
393        // 1. marshal parameters into json-array
394        napi_value paramArray = nullptr;
395        NAPI_CALL(env, napi_create_array_with_length(env, count, &paramArray));
396        if (count > NAPI_MAX_ARG_COUNT) {
397            count = NAPI_MAX_ARG_COUNT;
398        }
399        for (size_t idx = 0; idx < count; idx++) {
400            napi_value item = nullptr; // convert to backendObjRef if any
401            NAPI_CALL(env, GetBackendObjRefProp(env, argv[idx], &item));
402            if (item == nullptr) {
403                item = argv[idx];
404            }
405            NAPI_CALL(env, napi_set_element(env, paramArray, idx, item));
406        }
407        napi_value jsTempObj = nullptr;
408        NAPI_CALL(env, ValueStringConvert(env, paramArray, &jsTempObj, true));
409        ctx.callInfo_.paramList_ = nlohmann::json::parse(JsStrToCppStr(env, jsTempObj));
410        // 2. marshal jsThis into json (backendObjRef)
411        if (!methodDef->static_) {
412            NAPI_CALL(env, GetBackendObjRefProp(env, ctx.jsThis_, &jsTempObj));
413            ctx.callInfo_.callerObjRef_ = JsStrToCppStr(env, jsTempObj);
414        }
415        // 3. fill-in apiId
416        ctx.callInfo_.apiId_ = methodDef->name_;
417        napi_value error = nullptr;
418        PreprocessTransaction(env, ctx, error);
419        if (error != nullptr) {
420            NAPI_CALL(env, napi_throw(env, error));
421            NAPI_CALL(env, napi_get_undefined(env, &error));
422            return error;
423        }
424        // 4. call out, sync or async
425        if (methodDef->fast_) {
426            return TransactSync(env, ctx);
427        } else {
428            return TransactAsync(env, ctx);
429        }
430    }
431
432    /**Exports uitest js wrapper-classes and its global constructor.*/
433    static napi_status ExportClass(napi_env env, napi_value exports, const FrontEndClassDef &classDef)
434    {
435        NAPI_ASSERT_BASE(env, exports != nullptr, "Illegal export params", NAPI_ERR);
436        const auto name = classDef.name_.data();
437        const auto methodNeatNameOffset = classDef.name_.length() + 1;
438        auto descs = make_unique<napi_property_descriptor[]>(classDef.methodCount_);
439        for (size_t idx = 0; idx < classDef.methodCount_; idx++) {
440            const auto &methodDef = classDef.methods_[idx];
441            g_unCalledJsFuncNames.insert(string(methodDef.name_));
442            const auto neatName = methodDef.name_.substr(methodNeatNameOffset);
443            napi_property_descriptor desc = DECLARE_NAPI_FUNCTION(neatName.data(), GenericCallback);
444            if (methodDef.static_) {
445                desc.attributes = napi_static;
446            }
447            desc.data = (void *)(&methodDef);
448            descs[idx] = desc;
449        }
450        constexpr auto initializer = [](napi_env env, napi_callback_info info) {
451            auto argc = NAPI_MAX_ARG_COUNT;
452            napi_value argv[NAPI_MAX_ARG_COUNT] = { nullptr };
453            napi_value jsThis = nullptr;
454            NAPI_CALL_BASE(env, napi_get_cb_info(env, info, &argc, argv, &jsThis, nullptr), jsThis);
455            auto ref = make_unique<string>(argc <= 0 ? "" : JsStrToCppStr(env, argv[0]));
456            auto finalizer = [](napi_env env, void *data, void *hint) {
457                auto ref = reinterpret_cast<string *>(data);
458                if (ref->length() > 0) {
459                    LOG_D("Finalizing object: %{public}s", ref->c_str());
460                    unique_lock<mutex> lock(g_gcQueueMutex);
461                    g_backendObjsAboutToDelete.push(*ref);
462                }
463                delete ref;
464            };
465            NAPI_CALL_BASE(env, napi_wrap(env, jsThis, ref.release(), finalizer, nullptr, nullptr), jsThis);
466            return jsThis;
467        };
468        // define class, provide the js-class members(property) and initializer.
469        napi_value ctor = nullptr;
470        NAPI_CALL_BASE(env, napi_define_class(env, name, NAPI_AUTO_LENGTH, initializer, nullptr,
471                                              classDef.methodCount_, descs.get(), &ctor), NAPI_ERR);
472        NAPI_CALL_BASE(env, napi_set_named_property(env, exports, name, ctor), NAPI_ERR);
473        NAPI_CALL_BASE(env, MountJsConstructorToGlobal(env, name, ctor), NAPI_ERR);
474        if (string_view(name) == "On" || string_view(name) == "By") {
475            // create seed-On/By with special objectRef and mount to exporter
476            auto seedName = string_view(name) == "On" ? "ON" : "BY";
477            auto seedRef = string_view(name) == "On" ? REF_SEED_ON.data() : REF_SEED_BY.data();
478            napi_value seed = nullptr;
479            NAPI_CALL_BASE(env, napi_new_instance(env, ctor, 0, nullptr, &seed), NAPI_ERR);
480            napi_value prop = nullptr;
481            NAPI_CALL_BASE(env, napi_create_string_utf8(env, seedRef, NAPI_AUTO_LENGTH, &prop), NAPI_ERR);
482            NAPI_CALL_BASE(env, napi_set_named_property(env, seed, PROP_BACKEND_OBJ_REF, prop), NAPI_ERR);
483            NAPI_CALL_BASE(env, napi_set_named_property(env, exports, seedName, seed), NAPI_ERR);
484        }
485        return napi_ok;
486    }
487
488    /**Exports enumerator class.*/
489    static napi_status ExportEnumerator(napi_env env, napi_value exports, const FrontendEnumeratorDef &enumDef)
490    {
491        NAPI_ASSERT_BASE(env, exports != nullptr, "Illegal export params", NAPI_ERR);
492        napi_value enumerator;
493        NAPI_CALL_BASE(env, napi_create_object(env, &enumerator), NAPI_ERR);
494        for (size_t idx = 0; idx < enumDef.valueCount_; idx++) {
495            const auto &def = enumDef.values_[idx];
496            napi_value prop = nullptr;
497            NAPI_CALL_BASE(env, napi_create_string_utf8(env, def.valueJson_.data(), NAPI_AUTO_LENGTH, &prop), NAPI_ERR);
498            NAPI_CALL_BASE(env, ValueStringConvert(env, prop, &prop, false), NAPI_ERR);
499            NAPI_CALL_BASE(env, napi_set_named_property(env, enumerator, def.name_.data(), prop), NAPI_ERR);
500        }
501        NAPI_CALL_BASE(env, napi_set_named_property(env, exports, enumDef.name_.data(), enumerator), NAPI_ERR);
502        return napi_ok;
503    }
504
505    /**Function used for statistics, return an array of uncalled js-api names.*/
506    static napi_value GetUnCalledJsApis(napi_env env, napi_callback_info info)
507    {
508        napi_value nameArray;
509        NAPI_CALL(env, napi_create_array_with_length(env, g_unCalledJsFuncNames.size(), &nameArray));
510        size_t idx = 0;
511        for (auto &name : g_unCalledJsFuncNames) {
512            napi_value nameItem = nullptr;
513            NAPI_CALL(env, napi_create_string_utf8(env, name.c_str(), NAPI_AUTO_LENGTH, &nameItem));
514            NAPI_CALL(env, napi_set_element(env, nameArray, idx, nameItem));
515            idx++;
516        }
517        return nameArray;
518    }
519
520    napi_value Export(napi_env env, napi_value exports)
521    {
522        LOG_I("Begin export uitest apis");
523        // export transaction-environment-lifecycle callbacks and dfx functions
524        napi_property_descriptor props[] = {
525            DECLARE_NAPI_STATIC_FUNCTION("scheduleEstablishConnection", ScheduleEstablishConnection),
526            DECLARE_NAPI_STATIC_FUNCTION("getUnCalledJsApis", GetUnCalledJsApis),
527        };
528        NAPI_CALL(env, napi_define_properties(env, exports, sizeof(props) / sizeof(props[0]), props));
529        NAPI_CALL(env, ExportClass(env, exports, BY_DEF));
530        NAPI_CALL(env, ExportClass(env, exports, UI_DRIVER_DEF));
531        NAPI_CALL(env, ExportClass(env, exports, UI_COMPONENT_DEF));
532        NAPI_CALL(env, ExportClass(env, exports, ON_DEF));
533        NAPI_CALL(env, ExportClass(env, exports, DRIVER_DEF));
534        NAPI_CALL(env, ExportClass(env, exports, COMPONENT_DEF));
535        NAPI_CALL(env, ExportClass(env, exports, UI_WINDOW_DEF));
536        NAPI_CALL(env, ExportClass(env, exports, POINTER_MATRIX_DEF));
537        NAPI_CALL(env, ExportClass(env, exports, UI_EVENT_OBSERVER_DEF));
538        NAPI_CALL(env, ExportEnumerator(env, exports, MATCH_PATTERN_DEF));
539        NAPI_CALL(env, ExportEnumerator(env, exports, RESIZE_DIRECTION_DEF));
540        NAPI_CALL(env, ExportEnumerator(env, exports, WINDOW_MODE_DEF));
541        NAPI_CALL(env, ExportEnumerator(env, exports, DISPLAY_ROTATION_DEF));
542        NAPI_CALL(env, ExportEnumerator(env, exports, MOUSE_BUTTON_DEF));
543        NAPI_CALL(env, ExportEnumerator(env, exports, UI_DIRECTION_DEF));
544        LOG_I("End export uitest apis");
545        return exports;
546    }
547
548    static napi_module module = {
549        .nm_version = 1,
550        .nm_flags = 0,
551        .nm_filename = nullptr,
552        .nm_register_func = Export,
553        .nm_modname = "UiTest",
554        .nm_priv = ((void *)0),
555        .reserved = {0},
556    };
557
558    extern "C" __attribute__((constructor)) void RegisterModule(void)
559    {
560        napi_module_register(&module);
561    }
562} // namespace OHOS::uitest
563
564// put register functions out of namespace to ensure C-linkage
565extern const char _binary_uitest_exporter_js_start[];
566extern const char _binary_uitest_exporter_js_end[];
567extern const char _binary_uitest_exporter_abc_start[];
568extern const char _binary_uitest_exporter_abc_end[];
569
570extern "C" __attribute__((visibility("default"))) void NAPI_UiTest_GetJSCode(const char **buf, int *bufLen)
571{
572    if (buf != nullptr) {
573        *buf = _binary_uitest_exporter_js_start;
574    }
575    if (bufLen != nullptr) {
576        *bufLen = _binary_uitest_exporter_js_end - _binary_uitest_exporter_js_start;
577    }
578}
579
580extern "C" __attribute__((visibility("default"))) void NAPI_UiTest_GetABCCode(const char **buf, int *bufLen)
581{
582    if (buf != nullptr) {
583        *buf = _binary_uitest_exporter_abc_start;
584    }
585    if (bufLen != nullptr) {
586        *bufLen = _binary_uitest_exporter_abc_end - _binary_uitest_exporter_abc_start;
587    }
588}
589