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