1 /* 2 * Copyright (c) 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 #ifndef NWEB_WEBVIEW_JAVASCRIPT_RESULT_CALLBACK_IMPL_H 17 #define NWEB_WEBVIEW_JAVASCRIPT_RESULT_CALLBACK_IMPL_H 18 19 #include <condition_variable> 20 #include <mutex> 21 #include <string> 22 #include <unordered_map> 23 #include <unordered_set> 24 #include <vector> 25 26 #include "napi/native_api.h" 27 #include "napi/native_common.h" 28 #include "napi/native_node_api.h" 29 #include "napi_parse_utils.h" 30 #include "nweb.h" 31 #include "nweb_javascript_result_callback.h" 32 #include "nweb_log.h" 33 #include "nweb_value.h" 34 #include "uv.h" 35 36 namespace OHOS::NWeb { 37 38 class JavaScriptOb { 39 public: 40 // to be compatible with older webcotroller, be sure to the same as ace and core 41 enum class JavaScriptObjIdErrorCode : int32_t { WEBCONTROLLERERROR = -2, WEBVIEWCONTROLLERERROR = -1, END = 0 }; 42 43 typedef int32_t ObjectID; 44 45 static std::shared_ptr<JavaScriptOb> CreateNamed( 46 napi_env env, int32_t containerScopeId, napi_value value, size_t refCount = 1) 47 { 48 return std::make_shared<JavaScriptOb>(env, containerScopeId, value, refCount); 49 } 50 static std::shared_ptr<JavaScriptOb> CreateTransient( 51 napi_env env, int32_t containerScopeId, napi_value value, int32_t holder, size_t refCount = 1) 52 { 53 std::set<int32_t> holders; 54 holders.insert(holder); 55 return std::make_shared<JavaScriptOb>(env, containerScopeId, value, holders, refCount); 56 } 57 58 JavaScriptOb(napi_env env, int32_t containerScopeId, napi_value value, size_t refCount = 1) env_(env)59 : env_(env), containerScopeId_(containerScopeId), isStrongRef_(refCount != 0), namesCount_(1) 60 { 61 napi_status s = napi_create_reference(env, value, refCount, &objRef_); 62 if (s != napi_ok) { 63 WVLOG_E("create javascript obj fail"); 64 } 65 } 66 67 JavaScriptOb( 68 napi_env env, int32_t containerScopeId, napi_value value, std::set<int32_t> holders, size_t refCount = 1) env_(env)69 : env_(env), containerScopeId_(containerScopeId), isStrongRef_(refCount != 0), namesCount_(0), holders_(holders) 70 { 71 std::unique_lock<std::mutex> lock(mutex_); 72 napi_status s = napi_create_reference(env, value, refCount, &objRef_); 73 if (s != napi_ok) { 74 WVLOG_E("create javascript obj fail"); 75 } 76 } 77 JavaScriptOb(const JavaScriptOb & job)78 JavaScriptOb(const JavaScriptOb& job) 79 { 80 *this = job; 81 } 82 JavaScriptOb(JavaScriptOb && job)83 JavaScriptOb(JavaScriptOb&& job) 84 { 85 *this = std::move(job); 86 } 87 88 JavaScriptOb& operator=(const JavaScriptOb& job) 89 { 90 if (this != &job) { 91 Delete(); 92 env_ = job.env_; 93 isStrongRef_ = job.isStrongRef_; 94 if (isStrongRef_) { 95 objRef_ = job.objRef_; 96 napi_status s = napi_reference_ref(env_, objRef_, nullptr); 97 if (s != napi_ok) { 98 WVLOG_E("JavaScriptOb copy assign fail"); 99 } 100 } else { 101 napi_status s = CreateNewWeakRef(env_, job.objRef_, &objRef_); 102 if (s != napi_ok) { 103 WVLOG_E("JavaScriptOb copy assign fail"); 104 } 105 } 106 } 107 return *this; 108 } 109 110 JavaScriptOb& operator=(JavaScriptOb&& job) 111 { 112 if (this != &job) { 113 Delete(); 114 env_ = job.env_; 115 objRef_ = job.objRef_; 116 isStrongRef_ = job.isStrongRef_; 117 job.env_ = nullptr; 118 job.objRef_ = nullptr; 119 } 120 return *this; 121 } 122 ~JavaScriptOb()123 ~JavaScriptOb() 124 { 125 Delete(); 126 } 127 GetEnv()128 napi_env GetEnv() const 129 { 130 return env_; 131 } 132 GetContainerScopeId()133 int32_t GetContainerScopeId() const 134 { 135 return containerScopeId_; 136 } 137 IsEmpty()138 bool IsEmpty() const 139 { 140 return !objRef_; 141 } 142 IsStrongRef()143 bool IsStrongRef() 144 { 145 return isStrongRef_; 146 } 147 GetValue()148 napi_value GetValue() const 149 { 150 napi_value result = nullptr; 151 napi_get_reference_value(env_, objRef_, &result); 152 return result; 153 } 154 ToWeakRef()155 void ToWeakRef() 156 { 157 if (!isStrongRef_ || !objRef_) { 158 return; 159 } 160 161 if (Release() == 0) { 162 isStrongRef_ = false; 163 return; 164 } 165 166 isStrongRef_ = false; 167 napi_status s = CreateNewWeakRef(env_, objRef_, &objRef_); 168 if (s != napi_ok) { 169 WVLOG_E("JavaScriptOb ToWeakRef fail"); 170 } 171 } 172 IsNamed()173 bool IsNamed() 174 { 175 return namesCount_ > 0; 176 } AddName()177 void AddName() 178 { 179 ++namesCount_; 180 } RemoveName()181 void RemoveName() 182 { 183 --namesCount_; 184 } 185 HasHolders()186 bool HasHolders() 187 { 188 return !holders_.empty(); 189 } AddHolder(int32_t holder)190 void AddHolder(int32_t holder) 191 { 192 holders_.insert(holder); 193 } RemoveHolder(int32_t holder)194 void RemoveHolder(int32_t holder) 195 { 196 holders_.erase(holder); 197 } 198 GetMethodNames()199 std::vector<std::string> GetMethodNames() 200 { 201 if (!isMethodsSetup_) { 202 SetUpMethods(); 203 } 204 return methods_; 205 } 206 HasMethod(const std::string & methodName)207 bool HasMethod(const std::string& methodName) 208 { 209 if (methodName.empty()) { 210 WVLOG_E("HasMethod methodName null"); 211 return false; 212 } 213 214 if (!isMethodsSetup_) { 215 SetUpMethods(); 216 } 217 for (std::vector<std::string>::iterator iter = methods_.begin(); iter != methods_.end(); ++iter) { 218 if (*iter == methodName) { 219 return true; 220 } 221 } 222 return false; 223 } 224 FindMethod(const std::string & methodName)225 napi_value FindMethod(const std::string& methodName) 226 { 227 if (!isMethodsSetup_) { 228 SetUpMethods(); 229 } 230 231 if (HasMethod(methodName)) { 232 bool hasFunc = false; 233 napi_value result = nullptr; 234 napi_valuetype valueType = napi_undefined; 235 napi_value obj = GetValue(); 236 if (!obj) { 237 WVLOG_E("JavaScriptOb FindMethod obj null"); 238 return nullptr; 239 } 240 napi_status s = napi_has_named_property(env_, obj, methodName.c_str(), &hasFunc); 241 if (s != napi_ok) { 242 WVLOG_E("JavaScriptOb FindMethod fail"); 243 return nullptr; 244 } 245 if (!hasFunc) { 246 WVLOG_E("JavaScriptOb FindMethod fail"); 247 return nullptr; 248 } 249 s = napi_get_named_property(env_, obj, methodName.c_str(), &result); 250 if (s != napi_ok) { 251 WVLOG_E("JavaScriptOb FindMethod fail"); 252 return nullptr; 253 } 254 napi_typeof(env_, result, &valueType); 255 if (valueType != napi_function) { 256 WVLOG_E("JavaScriptOb FindMethod not function"); 257 return nullptr; 258 } 259 return result; 260 } 261 262 return nullptr; 263 } 264 SetUpMethods()265 void SetUpMethods() 266 { 267 napi_value propertyNames; 268 napi_value obj = GetValue(); 269 napi_status s = napi_get_all_property_names(env_, obj, napi_key_include_prototypes, napi_key_all_properties, 270 napi_key_numbers_to_strings, &propertyNames); 271 if (s != napi_ok) { 272 WVLOG_E("JavaScriptOb SetUpMethods fail"); 273 return; 274 } 275 uint32_t size; 276 s = napi_get_array_length(env_, propertyNames, &size); 277 if (s != napi_ok) { 278 WVLOG_E("JavaScriptOb SetUpMethods fail"); 279 return; 280 } 281 for (uint32_t i = 0; i < size; i++) { 282 napi_value napiKeyTmp; 283 s = napi_get_element(env_, propertyNames, i, &napiKeyTmp); 284 if (s != napi_ok) { 285 WVLOG_E("JavaScriptOb SetUpMethods fail"); 286 return; 287 } 288 napi_valuetype valueType = napi_undefined; 289 napi_value napiValueTmp; 290 s = napi_get_property(env_, obj, napiKeyTmp, &napiValueTmp); 291 if (s != napi_ok) { 292 WVLOG_E("JavaScriptOb SetUpMethods fail"); 293 return; 294 } 295 napi_typeof(env_, napiValueTmp, &valueType); 296 if (valueType != napi_function) { 297 continue; 298 } 299 std::string methodName; 300 if (NapiParseUtils::ParseString(env_, napiKeyTmp, methodName)) { 301 std::unique_lock<std::mutex> lock(mutex_); 302 methods_.push_back(methodName); 303 } 304 } 305 std::unique_lock<std::mutex> lock(mutex_); 306 isMethodsSetup_ = true; 307 } 308 SetMethods(std::vector<std::string> methods_name)309 void SetMethods(std::vector<std::string> methods_name) 310 { 311 std::unique_lock<std::mutex> lock(mutex_); 312 methods_ = methods_name; 313 isMethodsSetup_ = true; 314 } 315 316 private: CreateNewWeakRef(napi_env env,napi_ref ref,napi_ref * new_ref)317 static napi_status CreateNewWeakRef(napi_env env, napi_ref ref, napi_ref* new_ref) 318 { 319 napi_value val = nullptr; 320 napi_status sts = napi_get_reference_value(env, ref, &val); 321 if (sts != napi_ok) 322 return sts; 323 return napi_create_reference(env, val, 0, new_ref); 324 } 325 Delete()326 void Delete() 327 { 328 if (objRef_ && Release() == 0) { 329 WVLOG_E("JavaScriptOb delete called"); 330 napi_delete_reference(env_, objRef_); 331 objRef_ = nullptr; 332 } 333 } 334 Release()335 uint32_t Release() 336 { 337 if (!objRef_ || !isStrongRef_) { 338 return 0; 339 } 340 uint32_t refCount = 0; 341 napi_status s = napi_reference_unref(env_, objRef_, &refCount); 342 if (s != napi_ok) { 343 WVLOG_E("JavaScriptOb Release fail"); 344 } 345 return refCount; 346 } 347 348 napi_env env_ = nullptr; 349 int32_t containerScopeId_ = -1; 350 351 napi_ref objRef_ = nullptr; 352 bool isStrongRef_ = true; 353 354 std::vector<std::string> methods_; 355 // An object must be kept in retainedObjectSet_ either if it has 356 // names or if it has a non-empty holders set. 357 int namesCount_; 358 std::set<int32_t> holders_; 359 bool isMethodsSetup_ = false; 360 std::mutex mutex_; 361 }; 362 363 class WebviewJavaScriptResultCallBack : public NWebJavaScriptResultCallBack { 364 public: 365 typedef std::unordered_map<std::string, JavaScriptOb::ObjectID> NamedObjectMap; 366 typedef std::unordered_map<JavaScriptOb::ObjectID, std::shared_ptr<JavaScriptOb>> ObjectMap; 367 typedef int32_t ObjectID; 368 struct H5Bundle { 369 int32_t nwebId; 370 int32_t frameRoutingId; 371 int32_t h5Id; 372 std::string funcName; 373 }; 374 375 enum class NapiJsCallBackParmFlag : int32_t { ISOBJECT = 1, END = 2 }; 376 377 struct NapiJsCallBackInParm { 378 WebviewJavaScriptResultCallBack* webJsResCb = nullptr; 379 int32_t nwebId = -1; 380 int32_t frameRoutingId = -1; 381 int32_t objId = -1; 382 std::string objName; 383 std::string methodName; 384 void* data = nullptr; 385 }; 386 387 struct NapiJsCallBackOutParm { 388 napi_status status; 389 int32_t errCode = -1; 390 NapiJsCallBackParmFlag flag; 391 void* ret = nullptr; 392 }; 393 394 struct NapiJsCallBackParm { 395 // environment 396 napi_env env = nullptr; 397 int32_t containerScopedId = -1; 398 399 std::mutex mutex; 400 // sync 401 std::condition_variable condition; 402 bool ready = false; 403 // async 404 napi_async_work asyncWork = nullptr; 405 napi_deferred deferred = nullptr; 406 napi_ref callbackRef = nullptr; 407 // input 408 void* input = nullptr; 409 // out 410 void* out = nullptr; 411 }; 412 WebviewJavaScriptResultCallBack()413 WebviewJavaScriptResultCallBack() {} 414 415 explicit WebviewJavaScriptResultCallBack(std::weak_ptr<OHOS::NWeb::NWeb>& nweb, int32_t nwebId); 416 417 ~WebviewJavaScriptResultCallBack() override; 418 419 std::shared_ptr<NWebValue> GetJavaScriptResult(std::vector<std::shared_ptr<NWebValue>> args, 420 const std::string& method, const std::string& objName, int32_t routingId, int32_t objectId) override; 421 422 bool HasJavaScriptObjectMethods(int32_t objectId, const std::string& methodName) override; 423 424 std::shared_ptr<NWebValue> GetJavaScriptObjectMethods(int32_t objectId) override; 425 426 std::shared_ptr<JavaScriptOb> FindObject(JavaScriptOb::ObjectID objectId); 427 428 void RemoveJavaScriptObjectHolder(int32_t holder, JavaScriptOb::ObjectID objectId) override; 429 430 void RemoveJavaScriptObjectHolderInJsTd(int32_t holder, JavaScriptOb::ObjectID objectId); 431 432 void RemoveTransientJavaScriptObject() override; 433 434 bool FindObjectIdInJsTd(napi_env env, napi_value object, JavaScriptOb::ObjectID* objectId); 435 436 std::unordered_map<std::string, std::shared_ptr<JavaScriptOb>> GetNamedObjects(); 437 438 ObjectMap GetObjectMap(); 439 440 JavaScriptOb::ObjectID AddObject(napi_env env, const napi_value& object, bool methodName, int32_t holder); 441 442 void SetUpAnnotateMethods(JavaScriptOb::ObjectID objId, std::vector<std::string>& methodNameList); 443 444 JavaScriptOb::ObjectID RegisterJavaScriptProxy( 445 napi_env env, napi_value obj, const std::string& objName, const std::vector<std::string>& methodList); 446 447 bool DeleteJavaScriptRegister(const std::string& objName); 448 449 void CallH5FunctionInternal(napi_env env, H5Bundle& bundle, std::vector<std::shared_ptr<NWebValue>> nwebArgs); 450 GetNWebId()451 int32_t GetNWebId() 452 { 453 return nwebId_; 454 } 455 456 private: 457 bool RemoveNamedObject(const std::string& name); 458 459 JavaScriptOb::ObjectID AddNamedObject(napi_env env, napi_value& obj, const std::string& objName); 460 461 std::shared_ptr<NWebValue> PostGetJavaScriptResultToJsThread(std::vector<std::shared_ptr<NWebValue>> args, 462 const std::string& method, const std::string& objName, int32_t routingId, int32_t objectId); 463 464 bool PostHasJavaScriptObjectMethodsToJsThread(int32_t objectId, const std::string& methodName); 465 std::shared_ptr<NWebValue> PostGetJavaScriptObjectMethodsToJsThread(int32_t objectId); 466 467 void PostRemoveJavaScriptObjectHolderToJsThread(int32_t holder, JavaScriptOb::ObjectID objectId); 468 469 std::shared_ptr<NWebValue> GetJavaScriptResultSelf(std::vector<std::shared_ptr<NWebValue>> args, 470 const std::string& method, const std::string& objName, int32_t routingId, int32_t objectId); 471 472 std::weak_ptr<OHOS::NWeb::NWeb> nweb_; 473 int32_t nwebId_ = -1; 474 475 JavaScriptOb::ObjectID nextObjectId_ = 1; 476 NamedObjectMap namedObjects_; 477 ObjectMap objects_; 478 std::unordered_set<std::shared_ptr<JavaScriptOb>> retainedObjectSet_; 479 }; 480 } 481 #endif 482