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 "nlohmann/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 auto task = [context]() { 141 napi_value global = nullptr; 142 napi_value jsonProp = nullptr; 143 napi_value parseFunc = nullptr; 144 napi_get_global(context->env, &global); 145 napi_get_named_property(context->env, global, "JSON", &jsonProp); 146 napi_get_named_property(context->env, jsonProp, "parse", &parseFunc); 147 napi_value argv[1] = { nullptr }; 148 napi_create_string_utf8(context->env, context->elmentInfo.dump().c_str(), NAPI_AUTO_LENGTH, argv); 149 napi_call_function(context->env, jsonProp, parseFunc, 1, argv, argv); 150 napi_value jsCallback = nullptr; 151 napi_get_reference_value(context->env, context->callbackRef, &jsCallback); 152 napi_call_function(context->env, nullptr, jsCallback, 1, argv, argv); 153 // accessing callback from js-caller, do check and warn 154 auto hasError = false; 155 napi_is_exception_pending(context->env, &hasError); 156 if (hasError) { 157 LOG_W("Exception raised during call js_cb_function!"); 158 } 159 if (context->releaseObserver) { 160 LOG_D("Unref jsObserver: %{public}s", context->observerId.c_str()); 161 napi_delete_reference(context->env, context->observerRef); 162 g_jsRefs.erase(context->observerId); 163 } 164 if (context->releaseCallback) { 165 LOG_D("Unref jsCallback: %{public}s", context->callbackId.c_str()); 166 napi_delete_reference(context->env, context->callbackRef); 167 g_jsRefs.erase(context->callbackId); 168 } 169 delete context; 170 }; 171 if (napi_send_event(env, task, napi_eprio_immediate) != napi_status::napi_ok) { 172 LOG_E("Exception raised during call js_cb_function!"); 173 } 174 } 175 } 176