1 /* 2 * Copyright (c) 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 <set> 17 #include <string> 18 #include <unistd.h> 19 #include <uv.h> 20 #include "json.hpp" 21 #include "common_utilities_hpp.h" 22 #include "ui_event_observer_napi.h" 23 24 namespace OHOS::uitest { 25 using namespace nlohmann; 26 using namespace std; 27 28 /** Cached reference of UIEventObserver and JsCallbackFunction, key is the unique id.*/ 29 static map<string, napi_ref> g_jsRefs; 30 static size_t g_incJsCbId = 0; 31 Get()32 UiEventObserverNapi &UiEventObserverNapi::Get() 33 { 34 static UiEventObserverNapi instance; 35 return instance; 36 } 37 PreprocessCallOnce(napi_env env,ApiCallInfo & call,napi_value jsThis,napi_value * argv,ApiCallErr & err)38 void UiEventObserverNapi::PreprocessCallOnce(napi_env env, ApiCallInfo &call, napi_value jsThis, 39 napi_value* argv, ApiCallErr &err) 40 { 41 DCHECK(env != nullptr); 42 DCHECK(jsThis != nullptr); 43 auto ¶mList = call.paramList_; 44 // pop js_cb_function and save at local side 45 if (paramList.size() < 1 || paramList.at(0).type() != detail::value_t::string) { 46 LOG_E("Missing event type argument"); 47 err = ApiCallErr(ERR_INVALID_INPUT, "Missing event type argument"); 48 return; 49 } 50 auto event = paramList.at(0).get<string>(); 51 napi_valuetype type = napi_undefined; 52 if (paramList.size() > 1) { 53 NAPI_CALL_RETURN_VOID(env, napi_typeof(env, argv[1], &type)); 54 } 55 if (type != napi_function) { 56 LOG_E("Invalid callback function argument"); 57 err = ApiCallErr(ERR_INVALID_INPUT, "Invalid callback function argument"); 58 return; 59 } 60 if (g_jsRefs.find(call.callerObjRef_) == g_jsRefs.end()) { 61 // hold the jsObserver to avoid it be recycled, it's needed in performing callback 62 napi_ref ref = nullptr; 63 NAPI_CALL_RETURN_VOID(env, napi_create_reference(env, jsThis, 1, &ref)); 64 LOG_D("Hold reference of %{public}s", call.callerObjRef_.c_str()); 65 g_jsRefs.insert({ call.callerObjRef_, ref }); 66 } 67 auto jsCallback = argv[1]; 68 const auto jsCbId = string("js_callback#") + to_string(++g_incJsCbId); 69 // hold the const to avoid it be recycled, it's needed in performing callback 70 napi_ref ref = nullptr; 71 NAPI_CALL_RETURN_VOID(env, napi_create_reference(env, jsCallback, 1, &ref)); 72 LOG_I("CbId = %{public}s, CbRef = %{public}p", jsCbId.c_str(), ref); 73 LOG_D("Hold reference of %{public}s", jsCbId.c_str()); 74 g_jsRefs.insert({ jsCbId, ref }); 75 // pass jsCbId instread of the function body 76 paramList.at(1) = jsCbId; // observer.once(type, cllbackId) 77 } 78 79 struct EventCallbackContext { 80 napi_env env; 81 string observerId; 82 string callbackId; 83 napi_ref observerRef; 84 napi_ref callbackRef; 85 bool releaseObserver; // if or not release observer object after performing this callback 86 bool releaseCallback; // if or not release callback function after performing this callback 87 nlohmann::json elmentInfo; 88 }; 89 InitCallbackContext(napi_env env,const ApiCallInfo & in,ApiReplyInfo & out,EventCallbackContext & ctx)90 static void InitCallbackContext(napi_env env, const ApiCallInfo &in, ApiReplyInfo &out, EventCallbackContext &ctx) 91 { 92 LOG_I("Handler api callback: %{public}s", in.apiId_.c_str()); 93 if (in.apiId_ != "UIEventObserver.once") { 94 out.exception_ = ApiCallErr(ERR_INTERNAL, "Api dose not support callback: " + in.apiId_); 95 LOG_E("%{public}s", out.exception_.message_.c_str()); 96 return; 97 } 98 DCHECK(env != nullptr); 99 DCHECK(in.paramList_.size() > INDEX_ZERO && in.paramList_.at(INDEX_ZERO).type() == detail::value_t::object); 100 DCHECK(in.paramList_.size() > INDEX_ONE && in.paramList_.at(INDEX_ONE).type() == detail::value_t::string); 101 DCHECK(in.paramList_.size() > INDEX_TWO && in.paramList_.at(INDEX_TWO).type() == detail::value_t::boolean); 102 DCHECK(in.paramList_.size() > INDEX_THREE && in.paramList_.at(INDEX_THREE).type() == detail::value_t::boolean); 103 auto &observerId = in.callerObjRef_; 104 auto &elementInfo = in.paramList_.at(INDEX_ZERO); 105 auto callbackId = in.paramList_.at(INDEX_ONE).get<string>(); 106 LOG_I("Begin to callback UiEvent: observer=%{public}s, callback=%{public}s", 107 observerId.c_str(), callbackId.c_str()); 108 auto findObserver = g_jsRefs.find(observerId); 109 auto findCallback = g_jsRefs.find(callbackId); 110 if (findObserver == g_jsRefs.end()) { 111 out.exception_ = ApiCallErr(INTERNAL_ERROR, "UIEventObserver is not referenced: " + observerId); 112 LOG_E("%{public}s", out.exception_.message_.c_str()); 113 return; 114 } 115 if (findCallback == g_jsRefs.end()) { 116 out.exception_ = ApiCallErr(INTERNAL_ERROR, "JsCallbackFunction is not referenced: " + callbackId); 117 LOG_E("%{public}s", out.exception_.message_.c_str()); 118 return; 119 } 120 ctx.env = env; 121 ctx.observerId = observerId; 122 ctx.callbackId = callbackId; 123 ctx.observerRef = findObserver->second; 124 ctx.callbackRef = findCallback->second; 125 ctx.elmentInfo = move(elementInfo); 126 ctx.releaseObserver = in.paramList_.at(INDEX_TWO).get<bool>(); 127 ctx.releaseCallback = in.paramList_.at(INDEX_THREE).get<bool>(); 128 } 129 130 /**Handle api callback from server end.*/ HandleEventCallback(napi_env env,const ApiCallInfo & in,ApiReplyInfo & out)131 void UiEventObserverNapi::HandleEventCallback(napi_env env, const ApiCallInfo &in, ApiReplyInfo &out) 132 { 133 auto context = new EventCallbackContext(); 134 InitCallbackContext(env, in, out, *context); 135 if (out.exception_.code_ != NO_ERROR) { 136 delete context; 137 LOG_W("InitCallbackContext failed, cannot perform callback"); 138 return; 139 } 140 uv_loop_s *loop = nullptr; 141 NAPI_CALL_RETURN_VOID(env, napi_get_uv_event_loop(env, &loop)); 142 auto work = new uv_work_t; 143 work->data = context; 144 (void)uv_queue_work(loop, work, [](uv_work_t *) {}, [](uv_work_t *work, int status) { 145 auto ctx = reinterpret_cast<EventCallbackContext *>(work->data); 146 napi_value global = nullptr; 147 napi_value jsonProp = nullptr; 148 napi_value parseFunc = nullptr; 149 napi_get_global(ctx->env, &global); 150 napi_get_named_property(ctx->env, global, "JSON", &jsonProp); 151 napi_get_named_property(ctx->env, jsonProp, "parse", &parseFunc); 152 napi_value argv[1] = { nullptr }; 153 napi_create_string_utf8(ctx->env, ctx->elmentInfo.dump().c_str(), NAPI_AUTO_LENGTH, argv); 154 napi_call_function(ctx->env, jsonProp, parseFunc, 1, argv, argv); 155 napi_value jsCallback = nullptr; 156 napi_get_reference_value(ctx->env, ctx->callbackRef, &jsCallback); 157 napi_call_function(ctx->env, nullptr, jsCallback, 1, argv, argv); 158 // accessing callback from js-caller, do check and warn 159 auto hasError = false; 160 napi_is_exception_pending(ctx->env, &hasError); 161 if (hasError) { 162 LOG_W("Exception raised during call js_cb_function!"); 163 } 164 if (ctx->releaseObserver) { 165 LOG_D("Unref jsObserver: %{public}s", ctx->observerId.c_str()); 166 napi_delete_reference(ctx->env, ctx->observerRef); 167 g_jsRefs.erase(ctx->observerId); 168 } 169 if (ctx->releaseCallback) { 170 LOG_D("Unref jsCallback: %{public}s", ctx->callbackId.c_str()); 171 napi_delete_reference(ctx->env, ctx->callbackRef); 172 g_jsRefs.erase(ctx->callbackId); 173 } 174 delete work; 175 delete ctx; 176 }); 177 } 178 } 179