1/*
2 * Copyright (c) 2024 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 FOUNDATION_ACE_NAPI_NATIVE_ENGINE_NATIVE_ASYNC_HOOK_CONTEXT_H
17#define FOUNDATION_ACE_NAPI_NATIVE_ENGINE_NATIVE_ASYNC_HOOK_CONTEXT_H
18
19#include "callback_scope_manager/native_callback_scope_manager.h"
20#include "ecmascript/napi/include/jsnapi.h"
21#include "native_value.h"
22#include "native_engine.h"
23#include "utils/log.h"
24
25class NativeAsyncHookContext;
26static panda::JSValueRef* InternalMakeCallback(NativeEngine* engine, panda::FunctionRef* funRef,
27                                               panda::JSValueRef* obj, panda::JSValueRef *const argv[],
28                                               size_t argc, NativeAsyncHookContext* asyncContext);
29
30enum CallbackScopeCode {
31    CALLBACK_SCOPE_OK = 0,
32    CALLBACK_SCOPE_INVALID_ARG,
33    CALLBACK_SCOPE_MISMATCH,
34};
35
36class NativeAsyncHookContext {
37public:
38    NativeAsyncHookContext(NativeEngine* env,
39                           panda::Local<panda::ObjectRef> resourceObject,
40                           const panda::Local<panda::StringRef> resourceName,
41                           bool isExternalResource) : env_(env), resource_(env->GetEcmaVm(), resourceObject)
42    {
43        asyncId_ = env->NewAsyncId();
44        triggerAsyncId_ = env->GetDefaultTriggerAsyncId();
45        lostReference_ = false;
46        if (isExternalResource) {
47            resource_.SetWeak();
48            resource_.SetWeakCallback(reinterpret_cast<void*>(this), FreeGlobalCallBack, NativeFinalizeCallBack);
49        }
50
51        NativeAsyncWrap::EmitAsyncInit(env,
52                                       resourceObject,
53                                       resourceName,
54                                       asyncId_,
55                                       triggerAsyncId_);
56    }
57
58    ~NativeAsyncHookContext()
59    {
60        resource_.FreeGlobalHandleAddr();
61        lostReference_ = true;
62        NativeAsyncWrap::EmitDestroy(env_, asyncId_);
63    }
64
65    static void FreeGlobalCallBack(void* ref)
66    {
67        auto that = reinterpret_cast<NativeAsyncHookContext*>(ref);
68        that->resource_.FreeGlobalHandleAddr();
69        that->lostReference_ = true;
70    }
71
72    static void NativeFinalizeCallBack(void* ref) {}
73
74    inline AsyncIdInfo GetAsyncIdInfo()
75    {
76        return {asyncId_, triggerAsyncId_};
77    }
78
79    inline NativeCallbackScope* OpenCallbackScope()
80    {
81        EnsureReference();
82        auto callbackScopeManager = env_->GetCallbackScopeManager();
83        if (callbackScopeManager == nullptr) {
84            return nullptr;
85        }
86
87        auto callbackScope = callbackScopeManager->Open(env_, resource_.ToLocal(), GetAsyncIdInfo());
88        callbackScopeManager->IncrementOpenCallbackScopes();
89
90        return callbackScope;
91    }
92
93    inline void EnsureReference()
94    {
95        if (lostReference_) {
96            auto ecmaVm = env_->GetEcmaVm();
97            panda::Global<panda::ObjectRef> resource(ecmaVm, panda::ObjectRef::New(ecmaVm));
98            resource_ = resource;
99            lostReference_ = false;
100        }
101    }
102
103    static CallbackScopeCode CloseCallbackScope(NativeEngine* env, NativeCallbackScope* scope)
104    {
105        auto callbackScopeManager = env->GetCallbackScopeManager();
106        if (callbackScopeManager == nullptr) {
107            return CALLBACK_SCOPE_INVALID_ARG;
108        }
109        if (callbackScopeManager->GetOpenCallbackScopes() > 0) {
110            callbackScopeManager->DecrementOpenCallbackScopes();
111            callbackScopeManager->Close(scope);
112            return CALLBACK_SCOPE_OK;
113        } else {
114            return CALLBACK_SCOPE_MISMATCH;
115        }
116    }
117
118    panda::JSValueRef* MakeCallback(panda::FunctionRef* funRef, panda::JSValueRef* obj,
119                                    panda::JSValueRef *const argv[], size_t argc)
120    {
121        EnsureReference();
122        panda::JSValueRef* rst = InternalMakeCallback(env_, funRef, obj, argv, argc, this);
123        return rst;
124    }
125private:
126    NativeEngine* env_ {nullptr};
127    double asyncId_ = 0;
128    double triggerAsyncId_ = 0;
129    panda::Global<panda::ObjectRef> resource_;
130    bool lostReference_ = false;
131};
132
133static panda::FunctionRef* AsyncHooksCallbackTrampoline()
134{
135    return nullptr; // not support async_hook yet, and the actions need to be improved in the future
136}
137
138static void CloseScope(NativeAsyncHookContext* nativeAsyncContext,
139                       NativeCallbackScopeManager* callbackScopeMgr,
140                       NativeCallbackScope* callbackScope,
141                       NativeEngine* engine)
142{
143    if (nativeAsyncContext != nullptr) {
144        nativeAsyncContext->CloseCallbackScope(engine, callbackScope);
145    } else {
146        if (callbackScopeMgr != nullptr) {
147            callbackScopeMgr->Close(callbackScope);
148        }
149    }
150}
151
152static panda::JSValueRef* InternalMakeCallback(NativeEngine* engine, panda::FunctionRef* funRef,
153                                               panda::JSValueRef* obj, panda::JSValueRef* const argv[],
154                                               size_t argc, NativeAsyncHookContext* asyncContext)
155{
156    auto vm = engine->GetEcmaVm();
157    if (funRef == nullptr) {
158        HILOG_ERROR("funRef is nullptr!");
159        return *panda::JSValueRef::Undefined(vm);
160    }
161
162    bool useAsyncHooksTrampoline = false;
163    panda::FunctionRef* rstFunRef = AsyncHooksCallbackTrampoline();
164    if (rstFunRef != nullptr) {
165        useAsyncHooksTrampoline = true;
166    }
167    NativeCallbackScopeManager* callbackScopeMgr = nullptr;
168    NativeCallbackScope* callbackScope;
169    if (asyncContext == nullptr) {
170        callbackScopeMgr = engine->GetCallbackScopeManager();
171        callbackScope = callbackScopeMgr->Open(engine,
172            panda::Local<panda::ObjectRef>(reinterpret_cast<uintptr_t>(obj)), {0, 0});
173    } else {
174        callbackScope = asyncContext->OpenCallbackScope();
175    }
176
177    panda::JSValueRef* callBackRst;
178    if (useAsyncHooksTrampoline) {
179        callBackRst = nullptr; // not support async_hook yet, and the actions need to be improved in the future
180    } else {
181        callBackRst = funRef->CallForNapi(vm, obj, argv, argc);
182    }
183
184    if (callbackScope != nullptr) {
185        if (callBackRst == nullptr) {
186            callbackScope->MarkAsFailed();
187        }
188        callbackScope->Close();
189    }
190
191    CloseScope(asyncContext, callbackScopeMgr, callbackScope, engine);
192    return callBackRst;
193}
194
195panda::JSValueRef* MakeCallback(NativeEngine* engine, panda::FunctionRef* funRef,
196                                panda::JSValueRef* obj, size_t argc,
197                                panda::JSValueRef* const argv[], NativeAsyncHookContext* asyncContext)
198{
199    auto vm = engine->GetEcmaVm();
200    panda::JSValueRef* rst = InternalMakeCallback(engine, funRef, obj, argv, argc, asyncContext);
201    NativeCallbackScopeManager* callbackScopeMgr = engine->GetCallbackScopeManager();
202    size_t depth = callbackScopeMgr->GetAsyncCallbackScopeDepth();
203    if (rst == nullptr && depth == 0) {
204        return *panda::JSValueRef::Undefined(vm);
205    }
206    return rst;
207}
208
209#endif /* FOUNDATION_ACE_NAPI_NATIVE_ENGINE_NATIVE_ASYNC_HOOK_CONTEXT_H */
210