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 "nlohmann/json.hpp" 20 #include "common_utilities_hpp.h" 21 #include "ui_event_observer_ani.h" 22 #include "hilog/log.h" 23 using namespace OHOS::HiviewDFX; 24 25 constexpr OHOS::HiviewDFX::HiLogLabel LABEL = {LogType::LOG_CORE, 0xD003100, "UiTestKit"}; 26 27 namespace OHOS::uitest { 28 using namespace nlohmann; 29 using namespace std; 30 31 /** Cached reference of UIEventObserver and JsCallbackFunction, key is the unique id.*/ 32 static map<string, ani_ref> g_jsRefs; 33 static size_t g_incJsCbId = 0; 34 Get()35 UiEventObserverAni &UiEventObserverAni::Get() 36 { 37 static UiEventObserverAni instance; 38 return instance; 39 } 40 PreprocessCallOnce(ani_env * env,ApiCallInfo & call,ani_object & observerObj,ani_object & callbackObj,ApiReplyInfo & out)41 void UiEventObserverAni::PreprocessCallOnce(ani_env *env, ApiCallInfo &call, ani_object &observerObj, 42 ani_object &callbackObj, ApiReplyInfo &out) 43 { 44 DCHECK(env != nullptr); 45 auto ¶mList = call.paramList_; 46 // pop js_cb_function and save at local side 47 if (paramList.size() < 1 || paramList.at(0).type() != detail::value_t::string) { 48 HiLog::Error(LABEL,"Missing event type argument"); 49 out.exception_ = ApiCallErr(ERR_INVALID_INPUT, "Missing event type argument"); 50 return; 51 } 52 auto event = paramList.at(0).get<string>(); 53 if (g_jsRefs.find(call.callerObjRef_) == g_jsRefs.end()) { 54 // hold the jsObserver to avoid it be recycled, it's needed in performing callback 55 ani_ref observerRef; 56 if (env->GlobalReference_Create(static_cast<ani_ref>(observerObj), &observerRef) != ANI_OK) { 57 HiLog::Error(LABEL,"Create observerRef fail"); 58 out.exception_ = ApiCallErr(ERR_INTERNAL, "Create observerRef fail"); 59 return; 60 } 61 HiLog::Debug(LABEL,"Hold reference of %{public}s, ref: %{public}p", call.callerObjRef_.c_str(), observerRef); 62 g_jsRefs.insert({ call.callerObjRef_, observerRef }); 63 } 64 const auto jsCbId = string("js_callback#") + to_string(++g_incJsCbId); 65 // hold the const to avoid it be recycled, it's needed in performing callback 66 ani_ref callbackRef; 67 if (env->GlobalReference_Create(static_cast<ani_ref>(callbackObj), &callbackRef) != ANI_OK) { 68 HiLog::Error(LABEL,"Create callbackRef fail"); 69 out.exception_ = ApiCallErr(ERR_INTERNAL, "Create callbackRef fail"); 70 return; 71 } 72 HiLog::Info(LABEL,"CbId = %{public}s, CbRef = %{public}p", jsCbId.c_str(), callbackRef); 73 g_jsRefs.insert({ jsCbId, callbackRef }); 74 // pass jsCbId instread of the function body 75 call.paramList_.push_back(jsCbId); // observer.once(type, cllbackId) 76 } 77 78 struct EventCallbackContext { 79 ani_env *env; 80 string observerId; 81 string callbackId; 82 ani_ref observerRef; 83 ani_ref callbackRef; 84 bool releaseObserver; // if or not release observer object after performing this callback 85 bool releaseCallback; // if or not release callback function after performing this callback 86 nlohmann::json elmentInfo; 87 }; 88 InitCallbackContext(ani_env * env,const ApiCallInfo & in,ApiReplyInfo & out,EventCallbackContext & ctx)89 static void InitCallbackContext(ani_env *env, const ApiCallInfo &in, ApiReplyInfo &out, EventCallbackContext &ctx) 90 { 91 HiLog::Info(LABEL,"Handler api callback: %{public}s", in.apiId_.c_str()); 92 if (in.apiId_ != "UIEventObserver.once") { 93 out.exception_ = ApiCallErr(ERR_INTERNAL, "Api dose not support callback: " + in.apiId_); 94 HiLog::Error(LABEL,"%{public}s", out.exception_.message_.c_str()); 95 return; 96 } 97 DCHECK(env != nullptr); 98 DCHECK(in.paramList_.size() > INDEX_ZERO && in.paramList_.at(INDEX_ZERO).type() == detail::value_t::object); 99 DCHECK(in.paramList_.size() > INDEX_ONE && in.paramList_.at(INDEX_ONE).type() == detail::value_t::string); 100 DCHECK(in.paramList_.size() > INDEX_TWO && in.paramList_.at(INDEX_TWO).type() == detail::value_t::boolean); 101 DCHECK(in.paramList_.size() > INDEX_THREE && in.paramList_.at(INDEX_THREE).type() == detail::value_t::boolean); 102 auto &observerId = in.callerObjRef_; 103 auto &elementInfo = in.paramList_.at(INDEX_ZERO); 104 auto callbackId = in.paramList_.at(INDEX_ONE).get<string>(); 105 HiLog::Info(LABEL,"Begin to callback UiEvent: observer=%{public}s, callback=%{public}s", 106 observerId.c_str(), callbackId.c_str()); 107 auto findObserver = g_jsRefs.find(observerId); 108 auto findCallback = g_jsRefs.find(callbackId); 109 if (findObserver == g_jsRefs.end()) { 110 out.exception_ = ApiCallErr(INTERNAL_ERROR, "UIEventObserver is not referenced: " + observerId); 111 HiLog::Error(LABEL,"%{public}s", out.exception_.message_.c_str()); 112 return; 113 } 114 if (findCallback == g_jsRefs.end()) { 115 out.exception_ = ApiCallErr(INTERNAL_ERROR, "JsCallbackFunction is not referenced: " + callbackId); 116 HiLog::Error(LABEL,"%{public}s", out.exception_.message_.c_str()); 117 return; 118 } 119 ctx.env = env; 120 ctx.observerId = observerId; 121 ctx.callbackId = callbackId; 122 ctx.observerRef = findObserver->second; 123 ctx.callbackRef = findCallback->second; 124 ctx.elmentInfo = move(elementInfo); 125 ctx.releaseObserver = in.paramList_.at(INDEX_TWO).get<bool>(); 126 ctx.releaseCallback = in.paramList_.at(INDEX_THREE).get<bool>(); 127 } 128 129 /**Handle api callback from server end.*/ HandleEventCallback(ani_vm * vm,const ApiCallInfo & in,ApiReplyInfo & out)130 void UiEventObserverAni::HandleEventCallback(ani_vm *vm, const ApiCallInfo &in, ApiReplyInfo &out) 131 { 132 ani_env *env = nullptr; 133 ani_options aniArgs {0, nullptr}; 134 auto re = vm->AttachCurrentThread(&aniArgs, ANI_VERSION_1, &env); 135 if (re != ANI_OK) { 136 HiLog::Error(LABEL,"AttachCurrentThread fail, result: %{public}d", re); 137 out.exception_ = ApiCallErr(ERR_INTERNAL, "AttachCurrentThread fail"); 138 return; 139 } 140 auto context = new EventCallbackContext(); 141 InitCallbackContext(env, in, out, *context); 142 if (out.exception_.code_ != NO_ERROR) { 143 delete context; 144 HiLog::Warn(LABEL,"InitCallbackContext failed, cannot perform callback"); 145 vm->DetachCurrentThread(); 146 return; 147 } 148 auto bundleName = context->elmentInfo["bundleName"].get<string>(); 149 auto cType = context->elmentInfo["type"].get<string>(); 150 auto text = context->elmentInfo["text"].get<string>(); 151 ani_string strBundleName; 152 ani_string strType; 153 ani_string strText; 154 auto re1 = env->String_NewUTF8(bundleName.c_str(), bundleName.size(), &strBundleName); 155 auto re2 = env->String_NewUTF8(cType.c_str(), cType.size(), &strType); 156 auto re3 = env->String_NewUTF8(text.c_str(), text.size(), &strText); 157 if (re1 != ANI_OK || re2 != ANI_OK || re3 != ANI_OK) { 158 HiLog::Error(LABEL,"Analysis uielementInfo fail"); 159 out.exception_ = ApiCallErr(ERR_INTERNAL, "Analysis uielementInfo fail"); 160 vm->DetachCurrentThread(); 161 return; 162 } 163 static const char *className = "L@ohos/UiTest/UIElementInfoInner;"; 164 ani_class cls; 165 if (ANI_OK != env->FindClass(className, &cls)) { 166 HiLog::Error(LABEL,"Not found class UIElementInfoInner"); 167 out.exception_ = ApiCallErr(ERR_INTERNAL, "FindClass UIElementInfoInner fail"); 168 vm->DetachCurrentThread(); 169 return; 170 } 171 ani_method method; 172 if (ANI_OK != env->Class_FindMethod(cls, "<ctor>", ":V", &method)) { 173 HiLog::Error(LABEL,"Not found method of UIElementInfoInner"); 174 vm->DetachCurrentThread(); 175 return; 176 } 177 ani_object obj; 178 if (ANI_OK != env->Object_New(cls, method, &obj)) { 179 HiLog::Error(LABEL,"Object_New UIElementInfoInner fail"); 180 vm->DetachCurrentThread(); 181 return; 182 } 183 if (ANI_OK != env->Object_SetPropertyByName_Ref(obj, "bundleName", reinterpret_cast<ani_ref>(strBundleName))) { 184 HiLog::Error(LABEL,"SetProperty bundleName fail"); 185 vm->DetachCurrentThread(); 186 return; 187 } 188 if (ANI_OK != env->Object_SetPropertyByName_Ref(obj, "type", reinterpret_cast<ani_ref>(strType))) { 189 HiLog::Error(LABEL,"SetProperty type fail"); 190 vm->DetachCurrentThread(); 191 return; 192 } 193 if (ANI_OK != env->Object_SetPropertyByName_Ref(obj, "text", reinterpret_cast<ani_ref>(strText))) { 194 HiLog::Error(LABEL,"SetProperty bundleName fail"); 195 vm->DetachCurrentThread(); 196 return; 197 } 198 ani_ref argvRef = static_cast<ani_ref>(obj); 199 std::vector<ani_ref> tmp = {argvRef}; 200 ani_ref result; 201 if (ANI_OK != env->FunctionalObject_Call(reinterpret_cast<ani_fn_object>(context->callbackRef), tmp.size(), 202 tmp.data(), &result)) { 203 HiLog::Error(LABEL,"HandleEventCallback fail"); 204 vm->DetachCurrentThread(); 205 return; 206 } 207 if (context->releaseObserver) { 208 HiLog::Debug(LABEL,"Unref jsObserver: %{public}s", context->observerId.c_str()); 209 env->GlobalReference_Delete(context->observerRef); 210 g_jsRefs.erase(context->observerId); 211 } 212 if (context->releaseCallback) { 213 HiLog::Debug(LABEL,"Unref jsCallback: %{public}s", context->callbackId.c_str()); 214 env->GlobalReference_Delete(context->callbackRef); 215 g_jsRefs.erase(context->callbackId); 216 } 217 delete context; 218 vm->DetachCurrentThread(); 219 } 220 } 221