//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

#include "RuntimeLibraryPch.h"

#ifdef _CHAKRACOREBUILD
#include "Types/PathTypeHandler.h"

using namespace Js;

CustomExternalWrapperType::CustomExternalWrapperType(Js::ScriptContext* scriptContext, JsTraceCallback traceCallback, JsFinalizeCallback finalizeCallback, Js::RecyclableObject * prototype)
    : Js::DynamicType(
        scriptContext,
        Js::TypeIds_Object,
        prototype,
        nullptr,
        Js::PathTypeHandlerNoAttr::New(scriptContext, scriptContext->GetLibrary()->GetRootPath(), 0, 0, 0, true, true),
        true,
        true)
    , jsTraceCallback(traceCallback)
    , jsFinalizeCallback(finalizeCallback)
{
    this->jsGetterSetterInterceptor = RecyclerNewStructZ(scriptContext->GetRecycler(), JsGetterSetterInterceptor);
    this->flags |= TypeFlagMask_JsrtExternal;
}

CustomExternalWrapperObject::CustomExternalWrapperObject(CustomExternalWrapperType * type, void *data, uint inlineSlotSize) :
    Js::DynamicObject(type, false/* initSlots*/)
{
    if (inlineSlotSize != 0)
    {
        this->slotType = SlotType::Inline;
        this->u.inlineSlotSize = inlineSlotSize;
        if (data)
        {
            memcpy_s(this->GetInlineSlots(), inlineSlotSize, data, inlineSlotSize);
        }
    }
    else
    {
        this->slotType = SlotType::External;
        this->u.slot = data;
    }
}

CustomExternalWrapperObject::CustomExternalWrapperObject(CustomExternalWrapperObject * instance, bool deepCopy) :
    Js::DynamicObject(instance, deepCopy),
    initialized(instance->initialized)
{
    if (instance->GetInlineSlotSize() != 0)
    {
        this->slotType = SlotType::Inline;
        this->u.inlineSlotSize = instance->GetInlineSlotSize();
        if (instance->GetInlineSlots())
        {
            memcpy_s(this->GetInlineSlots(), this->GetInlineSlotSize(), instance->GetInlineSlots(), instance->GetInlineSlotSize());
        }
    }
    else
    {
        this->slotType = SlotType::External;
        this->u.slot = instance->GetInlineSlots();
    }
}

/* static */
CustomExternalWrapperObject* CustomExternalWrapperObject::Create(void *data, uint inlineSlotSize, JsTraceCallback traceCallback, JsFinalizeCallback finalizeCallback, JsGetterSetterInterceptor ** getterSetterInterceptor, Js::RecyclableObject * prototype, Js::ScriptContext *scriptContext)
{
    if (prototype == nullptr)
    {
        prototype = scriptContext->GetLibrary()->GetObjectPrototype();
    }

    Js::DynamicType * dynamicType = scriptContext->GetLibrary()->GetCachedCustomExternalWrapperType(reinterpret_cast<uintptr_t>(traceCallback), reinterpret_cast<uintptr_t>(finalizeCallback), reinterpret_cast<uintptr_t>(*getterSetterInterceptor), reinterpret_cast<uintptr_t>(prototype));
    if (dynamicType == nullptr)
    {
        dynamicType = RecyclerNew(scriptContext->GetRecycler(), CustomExternalWrapperType, scriptContext, traceCallback, finalizeCallback, prototype);
        *getterSetterInterceptor = reinterpret_cast<CustomExternalWrapperType *>(dynamicType)->GetJsGetterSetterInterceptor();
        scriptContext->GetLibrary()->CacheCustomExternalWrapperType(reinterpret_cast<uintptr_t>(traceCallback), reinterpret_cast<uintptr_t>(finalizeCallback), reinterpret_cast<uintptr_t>(*getterSetterInterceptor), reinterpret_cast<uintptr_t>(prototype), dynamicType);
    }
    else
    {
        if (*getterSetterInterceptor == nullptr)
        {
            *getterSetterInterceptor = reinterpret_cast<CustomExternalWrapperType *>(dynamicType)->GetJsGetterSetterInterceptor();
        }
        else
        {
            Assert(*getterSetterInterceptor == reinterpret_cast<CustomExternalWrapperType *>(dynamicType)->GetJsGetterSetterInterceptor());
        }
    }

    Assert(dynamicType->IsJsrtExternal());

    CustomExternalWrapperObject * externalObject;
    if (traceCallback != nullptr)
    {
        externalObject = RecyclerNewTrackedPlus(scriptContext->GetRecycler(), inlineSlotSize, CustomExternalWrapperObject, static_cast<CustomExternalWrapperType*>(dynamicType), data, inlineSlotSize);
    }
    else if (finalizeCallback != nullptr)
    {
        externalObject = RecyclerNewFinalizedPlus(scriptContext->GetRecycler(), inlineSlotSize, CustomExternalWrapperObject, static_cast<CustomExternalWrapperType*>(dynamicType), data, inlineSlotSize);
    }
    else
    {
        externalObject = RecyclerNewPlus(scriptContext->GetRecycler(), inlineSlotSize, CustomExternalWrapperObject, static_cast<CustomExternalWrapperType*>(dynamicType), data, inlineSlotSize);
    }

    return externalObject;
}

BOOL CustomExternalWrapperObject::EnsureInitialized(ScriptContext* requestContext)
{
    ThreadContext* threadContext = requestContext->GetThreadContext();
    if (this->initialized)
    {
        return TRUE;
    }
    void* initializeTrap = this->GetExternalType()->GetJsGetterSetterInterceptor()->initializeTrap;
    if (initializeTrap == nullptr)
    {
        return TRUE;
    }
    if (threadContext->IsDisableImplicitCall())
    {
        threadContext->AddImplicitCallFlags(Js::ImplicitCall_External);
        return FALSE;
    }

    this->initialized = true;
    JavascriptFunction * initializeMethod = Js::VarTo<JavascriptFunction>(initializeTrap);

    Js::RecyclableObject * targetObj = this;
    threadContext->ExecuteImplicitCall(initializeMethod, Js::ImplicitCall_Accessor, [=]()->Js::Var
    {
        return CALL_FUNCTION(threadContext, initializeMethod, Js::CallInfo(Js::CallFlags_Value, 1), targetObj);
    });
    return TRUE;
}

CustomExternalWrapperObject* CustomExternalWrapperObject::Copy(bool deepCopy)
{
    Recycler* recycler = this->GetRecycler();
    CustomExternalWrapperType* externalType = this->GetExternalType();
    int inlineSlotSize = this->GetInlineSlotSize();

    if (externalType->GetJsTraceCallback() != nullptr)
    {
        return RecyclerNewTrackedPlus(recycler, inlineSlotSize, CustomExternalWrapperObject, this, deepCopy);
    }
    else if (externalType->GetJsFinalizeCallback() != nullptr)
    {
        return RecyclerNewFinalizedPlus(recycler, inlineSlotSize, CustomExternalWrapperObject, this, deepCopy);
    }
    else
    {
        return RecyclerNewPlus(recycler, inlineSlotSize, CustomExternalWrapperObject, this, deepCopy);
    }
}

BOOL CustomExternalWrapperObject::IsObjectAlive()
{
    return !this->GetScriptContext()->IsClosed();
}

BOOL CustomExternalWrapperObject::VerifyObjectAlive()
{
    Js::ScriptContext* scriptContext = GetScriptContext();
    if (!scriptContext->VerifyAlive())
    {
        return FALSE;
    }

    // Perform an extended host object invalidation check only for external host objects.
    if (scriptContext->IsInvalidatedForHostObjects())
    {
        if (!scriptContext->GetThreadContext()->RecordImplicitException())
            return FALSE;
        Js::JavascriptError::MapAndThrowError(scriptContext, E_ACCESSDENIED);
    }
    return TRUE;
}

BOOL CustomExternalWrapperObject::Equals(__in Var other, __out BOOL* value, ScriptContext* requestContext)
{
    // We need to implement comparison to other by reference in case the object
    // is in the left side of the comparison, and does not call a toString
    // method (like different objects) when compared to string. For Node, such
    // comparision is used for Buffers.
    *value = (other == this);
    return true;
}

BOOL CustomExternalWrapperObject::StrictEquals(__in Var other, __out BOOL* value, ScriptContext* requestContext)
{
    // We need to implement comparison to other by reference in case the object
    // is in the left side of the comparison, and does not call a toString
    // method (like different objects) when compared to string. For Node, such
    // comparision is used for Buffers.
    *value = (other == this);
    return true;
}

void CustomExternalWrapperObject::Mark(Recycler * recycler)
{
    recycler->SetNeedExternalWrapperTracing();
    JsTraceCallback traceCallback = this->GetExternalType()->GetJsTraceCallback();
    Assert(nullptr != traceCallback);
    traceCallback(this->GetSlotData());
}

void CustomExternalWrapperObject::Finalize(bool isShutdown)
{
    JsFinalizeCallback finalizeCallback = this->GetExternalType()->GetJsFinalizeCallback();
    Assert(this->GetExternalType()->GetJsTraceCallback() != nullptr || finalizeCallback != nullptr);
    if (nullptr != finalizeCallback)
    {
        finalizeCallback(this->GetSlotData());
    }
}

void CustomExternalWrapperObject::Dispose(bool isShutdown)
{
}

void * CustomExternalWrapperObject::GetSlotData() const
{
    return this->slotType == SlotType::External
        ? unsafe_write_barrier_cast<void *>(this->u.slot)
        : GetInlineSlots();
}

void CustomExternalWrapperObject::SetSlotData(void * data)
{
    this->slotType = SlotType::External;
    this->u.slot = data;
}

int CustomExternalWrapperObject::GetInlineSlotSize() const
{
    return this->slotType == SlotType::External
        ? 0
        : this->u.inlineSlotSize;
}

void* CustomExternalWrapperObject::GetInlineSlots() const
{
    return this->slotType == SlotType::External
        ? nullptr
        : (void*)((uintptr_t)this + sizeof(CustomExternalWrapperObject));
}

Js::DynamicType* CustomExternalWrapperObject::DuplicateType()
{
    return RecyclerNew(this->GetScriptContext()->GetRecycler(), CustomExternalWrapperType, this->GetExternalType());
}

void CustomExternalWrapperObject::SetPrototype(RecyclableObject* newPrototype)
{
    if (!this->VerifyObjectAlive())
    {
        return;
    }

    // setting the value could be deferred and now we are on a different context from
    // make sure the value has the same context as the containing object.
    newPrototype = VarTo<RecyclableObject>(Js::CrossSite::MarshalVar(this->GetScriptContext(), newPrototype));
    DynamicObject::SetPrototype(newPrototype);
}

Js::Var CustomExternalWrapperObject::GetValueFromDescriptor(Js::Var instance, Js::PropertyDescriptor propertyDescriptor, Js::ScriptContext * requestContext)
{
    if (propertyDescriptor.ValueSpecified())
    {
        return propertyDescriptor.GetValue();
    }
    if (propertyDescriptor.GetterSpecified())
    {
        return Js::JavascriptOperators::CallGetter(VarTo<RecyclableObject>(propertyDescriptor.GetGetter()), instance, requestContext);
    }
    Assert(FALSE);
    return requestContext->GetLibrary()->GetUndefined();
}

void CustomExternalWrapperObject::PropertyIdFromInt(uint32 index, Js::PropertyRecord const** propertyRecord)
{
    char16 buffer[22];
    int pos = Js::TaggedInt::ToBuffer(index, buffer, _countof(buffer));

    GetScriptContext()->GetOrAddPropertyRecord((LPCWSTR)buffer + pos, (_countof(buffer) - 1) - pos, propertyRecord);
}

Js::Var CustomExternalWrapperObject::GetName(Js::ScriptContext* requestContext, Js::PropertyId propertyId, Var * isPropertyNameNumeric, Var * propertyNameNumericValue)
{
    const Js::PropertyRecord* propertyRecord = requestContext->GetThreadContext()->GetPropertyName(propertyId);
    Js::Var name;
    if (propertyRecord->IsSymbol())
    {
        name = requestContext->GetSymbol(propertyRecord);
    }
    else
    {
        name = requestContext->GetPropertyString(propertyRecord);
    }

    *isPropertyNameNumeric = propertyRecord->IsNumeric() ? requestContext->GetLibrary()->GetTrue() : requestContext->GetLibrary()->GetFalse();
    *propertyNameNumericValue = propertyRecord->IsNumeric() ? JavascriptNumber::ToVar(propertyRecord->GetNumericValue(), requestContext) : nullptr;
    return name;
}

template <class Fn, class GetPropertyNameFunc>
BOOL CustomExternalWrapperObject::GetPropertyTrap(Js::Var instance, Js::PropertyDescriptor * propertyDescriptor, Fn fn, GetPropertyNameFunc getPropertyName, Js::ScriptContext * requestContext, Js::PropertyValueInfo* info)
{
    PROBE_STACK(GetScriptContext(), Js::Constants::MinStackDefault);

    ThreadContext* threadContext = requestContext->GetThreadContext();

    if (!this->EnsureInitialized(requestContext))
    {
        return FALSE;
    }

    Js::RecyclableObject * targetObj = this;
    CustomExternalWrapperType * type = this->GetExternalType();
    Js::JavascriptFunction* getGetMethod = nullptr;
    if (type->GetJsGetterSetterInterceptor()->getTrap != nullptr)
    {
        getGetMethod = Js::VarTo<JavascriptFunction>(type->GetJsGetterSetterInterceptor()->getTrap);
    }

    if (nullptr == getGetMethod || requestContext->IsHeapEnumInProgress())
    {
        return fn(targetObj);
    }

    Js::Var isPropertyNameNumeric;
    Js::Var propertyNameNumericValue;
    Js::Var propertyName = getPropertyName(requestContext, &isPropertyNameNumeric, &propertyNameNumericValue);

    PropertyValueInfo::SetNoCache(info, targetObj);
    PropertyValueInfo::DisablePrototypeCache(info, targetObj);
    Js::Var getGetResult = threadContext->ExecuteImplicitCall(getGetMethod, Js::ImplicitCall_Accessor, [=]()->Js::Var
    {
        return CALL_FUNCTION(threadContext, getGetMethod, Js::CallInfo(Js::CallFlags_Value, 4), targetObj, propertyName, isPropertyNameNumeric, propertyNameNumericValue);
    });

    Js::TypeId getResultTypeId = Js::JavascriptOperators::GetTypeId(getGetResult);
    if (getResultTypeId == Js::TypeIds_Undefined)
    {
        return FALSE;
    }

    propertyDescriptor->SetValue(getGetResult);
    return TRUE;
}

template <class Fn, class GetPropertyNameFunc>
BOOL CustomExternalWrapperObject::HasPropertyTrap(Fn fn, GetPropertyNameFunc getPropertyName, Js::PropertyValueInfo* info)
{
    PROBE_STACK(GetScriptContext(), Js::Constants::MinStackDefault);

    ThreadContext* threadContext = GetScriptContext()->GetThreadContext();

    // Caller does not pass requestContext. Retrieve from host scriptContext stack.
    Js::ScriptContext* requestContext =
        threadContext->GetPreviousHostScriptContext()->GetScriptContext();

    if (!this->EnsureInitialized(requestContext))
    {
        return FALSE;
    }

    Js::RecyclableObject *targetObj = this;
    CustomExternalWrapperType * type = this->GetExternalType();
    Js::JavascriptFunction* hasMethod = nullptr;
    if (type->GetJsGetterSetterInterceptor()->hasTrap != nullptr)
    {
        hasMethod = Js::VarTo<JavascriptFunction>(type->GetJsGetterSetterInterceptor()->hasTrap);
    }

    if (nullptr == hasMethod || requestContext->IsHeapEnumInProgress())
    {
        return fn(targetObj);
    }

    Js::Var isPropertyNameNumeric;
    Js::Var propertyNameNumericValue;
    Js::Var propertyName = getPropertyName(requestContext, &isPropertyNameNumeric, &propertyNameNumericValue);

    PropertyValueInfo::SetNoCache(info, targetObj);
    PropertyValueInfo::DisablePrototypeCache(info, targetObj);
    Js::Var getHasResult = threadContext->ExecuteImplicitCall(hasMethod, Js::ImplicitCall_Accessor, [=]()->Js::Var
    {
        return CALL_FUNCTION(threadContext, hasMethod, Js::CallInfo(Js::CallFlags_Value, 4), targetObj, propertyName, isPropertyNameNumeric, propertyNameNumericValue);
    });

    BOOL hasProperty = Js::JavascriptConversion::ToBoolean(getHasResult, requestContext);
    return hasProperty;
}

Js::PropertyQueryFlags CustomExternalWrapperObject::HasPropertyQuery(Js::PropertyId propertyId, _Inout_opt_ Js::PropertyValueInfo* info)
{
    auto fn = [&](RecyclableObject* object)->BOOL {
        return JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::HasPropertyQuery(propertyId, info));
    };
    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        return GetName(requestContext, propertyId, isPropertyNameNumeric, propertyNameNumericValue);
    };
    return Js::JavascriptConversion::BooleanToPropertyQueryFlags(HasPropertyTrap(fn, getPropertyName, info));
}

BOOL CustomExternalWrapperObject::GetPropertyDescriptorTrap(Js::PropertyId propertyId, Js::PropertyDescriptor* resultDescriptor, Js::ScriptContext* requestContext)
{
    PROBE_STACK(GetScriptContext(), Js::Constants::MinStackDefault);

    ThreadContext* threadContext = requestContext->GetThreadContext();

    if (!this->EnsureInitialized(requestContext))
    {
        return FALSE;
    }

    Js::RecyclableObject * targetObj = this;

    CustomExternalWrapperType * type = this->GetExternalType();
    Js::JavascriptFunction* gOPDMethod = nullptr;
    if (type->GetJsGetterSetterInterceptor()->getOwnPropertyDescriptorTrap != nullptr)
    {
        gOPDMethod = Js::VarTo<JavascriptFunction>(type->GetJsGetterSetterInterceptor()->getOwnPropertyDescriptorTrap);
    }

    if (GetScriptContext()->IsHeapEnumInProgress())
    {
        return FALSE;
    }

    if (nullptr == gOPDMethod)
    {
        Var getter, setter;
        if (false == JavascriptOperators::GetOwnAccessors(this, propertyId, &getter, &setter, requestContext))
        {
            Var value = nullptr;
            if (false == JavascriptOperators::GetOwnProperty(this, propertyId, &value, requestContext, nullptr))
            {
                return FALSE;
            }
            if (nullptr != value)
            {
                resultDescriptor->SetValue(value);
            }

            resultDescriptor->SetWritable(FALSE != this->IsWritable(propertyId));
        }
        else
        {
            if (nullptr == getter)
            {
                getter = requestContext->GetLibrary()->GetUndefined();
            }
            resultDescriptor->SetGetter(getter);

            if (nullptr == setter)
            {
                setter = requestContext->GetLibrary()->GetUndefined();
            }
            resultDescriptor->SetSetter(setter);
        }

        resultDescriptor->SetConfigurable(FALSE != this->IsConfigurable(propertyId));
        resultDescriptor->SetEnumerable(FALSE != this->IsEnumerable(propertyId));
        return TRUE;
    }

    Js::Var isPropertyNameNumeric;
    Js::Var propertyNameNumericValue;
    Js::Var propertyName = GetName(requestContext, propertyId, &isPropertyNameNumeric, &propertyNameNumericValue);

    Assert(Js::VarIs<JavascriptString>(propertyName) || Js::VarIs<JavascriptSymbol>(propertyName));

    Js::Var getResult = threadContext->ExecuteImplicitCall(gOPDMethod, Js::ImplicitCall_Accessor, [=]()->Js::Var
    {
        return CALL_FUNCTION(threadContext, gOPDMethod, Js::CallInfo(Js::CallFlags_Value, 4), targetObj, propertyName, isPropertyNameNumeric, propertyNameNumericValue);
    });

    Js::TypeId getResultTypeId = Js::JavascriptOperators::GetTypeId(getResult);
    if (Js::StaticType::Is(getResultTypeId) && getResultTypeId != Js::TypeIds_Undefined)
    {
        Js::JavascriptError::ThrowTypeError(requestContext, JSERR_NeedObject, _u("getOwnPropertyDescriptor"));
    }

    BOOL toProperty = Js::JavascriptOperators::ToPropertyDescriptor(getResult, resultDescriptor, requestContext);
    Js::JavascriptOperators::CompletePropertyDescriptor(resultDescriptor, nullptr, requestContext);
    return toProperty;
}

BOOL CustomExternalWrapperObject::GetOwnPropertyDescriptor(Js::RecyclableObject * obj, Js::PropertyId propertyId, Js::ScriptContext * requestContext, Js::PropertyDescriptor * propertyDescriptor)
{
    CustomExternalWrapperObject * customObject = VarTo<CustomExternalWrapperObject>(obj);
    return customObject->GetPropertyDescriptorTrap(propertyId, propertyDescriptor, requestContext);
}

BOOL CustomExternalWrapperObject::DefineOwnPropertyDescriptor(Js::RecyclableObject * obj, Js::PropertyId propId, const Js::PropertyDescriptor& descriptor, bool throwOnError, Js::ScriptContext * requestContext)
{
    PROBE_STACK(requestContext, Js::Constants::MinStackDefault);

    ThreadContext* threadContext = requestContext->GetThreadContext();

    CustomExternalWrapperObject * customObject = VarTo<CustomExternalWrapperObject>(obj);

    if (!customObject->EnsureInitialized(requestContext))
    {
        return FALSE;
    }

    Js::RecyclableObject * targetObj = obj;

    CustomExternalWrapperType * type = customObject->GetExternalType();
    Js::JavascriptFunction* defineOwnPropertyMethod = nullptr;
    if (type->GetJsGetterSetterInterceptor()->definePropertyTrap != nullptr)
    {
        defineOwnPropertyMethod = Js::VarTo<JavascriptFunction>(type->GetJsGetterSetterInterceptor()->definePropertyTrap);
    }

    Assert(!requestContext->IsHeapEnumInProgress());
    if (nullptr == defineOwnPropertyMethod)
    {
        Js::PropertyDescriptor desc = descriptor;

        if(desc.ValueSpecified())
        {
            // setting the value could be deferred and now we are on a different context from
            // make sure the value has the same context as the containing object.
            desc.SetValue(Js::CrossSite::MarshalVar(targetObj->GetScriptContext(), descriptor.GetValue()));
        }

        PropertyDescriptor currentDescriptor;
        BOOL isCurrentDescriptorDefined = JavascriptOperators::GetOwnPropertyDescriptor(obj, propId, requestContext, &currentDescriptor);

        bool isExtensible = !!obj->IsExtensible();
        return Js::JavascriptOperators::ValidateAndApplyPropertyDescriptor<true>(obj, propId, desc, isCurrentDescriptorDefined ? &currentDescriptor : nullptr, isExtensible, throwOnError, requestContext);
    }

    Js::Var descVar = descriptor.GetOriginal();
    if (descVar == nullptr)
    {
        descVar = Js::JavascriptOperators::FromPropertyDescriptor(descriptor, requestContext);
    }

    Js::Var isPropertyNameNumeric;
    Js::Var propertyNameNumericValue;
    Js::Var propertyName = customObject->GetName(requestContext, propId, &isPropertyNameNumeric, &propertyNameNumericValue);

    Js::Var definePropertyResult = threadContext->ExecuteImplicitCall(defineOwnPropertyMethod, Js::ImplicitCall_Accessor, [=]()->Js::Var
    {
        return CALL_FUNCTION(threadContext, defineOwnPropertyMethod, Js::CallInfo(Js::CallFlags_Value, 5), targetObj, propertyName, isPropertyNameNumeric, propertyNameNumericValue, descVar);
    });

    BOOL defineResult = Js::JavascriptConversion::ToBoolean(definePropertyResult, requestContext);
    if (defineResult)
    {
        return defineResult;
    }

    Js::PropertyDescriptor targetDescriptor;
    BOOL hasProperty = Js::JavascriptOperators::GetOwnPropertyDescriptor(targetObj, propId, requestContext, &targetDescriptor);
    BOOL isExtensible = targetObj->IsExtensible();
    BOOL settingConfigFalse = (descriptor.ConfigurableSpecified() && !descriptor.IsConfigurable());
    if (!hasProperty)
    {
        if (!isExtensible || settingConfigFalse)
        {
            Js::JavascriptError::ThrowTypeError(requestContext, JSERR_InconsistentTrapResult, _u("defineProperty"));
        }
    }
    else
    {
        if (!Js::JavascriptOperators::IsCompatiblePropertyDescriptor(descriptor, hasProperty ? &targetDescriptor : nullptr, !!isExtensible, true, requestContext))
        {
            Js::JavascriptError::ThrowTypeError(requestContext, JSERR_InconsistentTrapResult, _u("defineProperty"));
        }
        if (settingConfigFalse && targetDescriptor.IsConfigurable())
        {
            Js::JavascriptError::ThrowTypeError(requestContext, JSERR_InconsistentTrapResult, _u("defineProperty"));
        }
    }
    return hasProperty;
}

BOOL CustomExternalWrapperObject::SetPropertyTrap(Js::Var receiver, SetPropertyTrapKind setPropertyTrapKind, Js::JavascriptString * propertyNameString, Js::Var newValue, Js::ScriptContext * requestContext, Js::PropertyOperationFlags propertyOperationFlags, Js::PropertyValueInfo* info)
{
    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        const Js::PropertyRecord* propertyRecord;
        propertyNameString->GetPropertyRecord(&propertyRecord);
        return GetName(requestContext, propertyRecord->GetPropertyId(), isPropertyNameNumeric, propertyNameNumericValue);
    };
    auto fn = [&]()->BOOL
    {
        // setting the value could be deferred and now we are on a different context from
        // make sure the value has the same context as the containing object.
        newValue = Js::CrossSite::MarshalVar(this->GetScriptContext(), newValue);
        return DynamicObject::SetProperty(propertyNameString, newValue, propertyOperationFlags, nullptr);
    };
    return SetPropertyTrap(receiver, setPropertyTrapKind, getPropertyName, newValue, requestContext, propertyOperationFlags, FALSE, fn, info);
}

template <class Fn, class GetPropertyNameFunc>
BOOL CustomExternalWrapperObject::SetPropertyTrap(Js::Var receiver, SetPropertyTrapKind setPropertyTrapKind, GetPropertyNameFunc getPropertyName, Js::Var newValue, Js::ScriptContext * requestContext, Js::PropertyOperationFlags propertyOperationFlags, BOOL skipPrototypeCheck, Fn fn, Js::PropertyValueInfo* info)
{
    PROBE_STACK(GetScriptContext(), Js::Constants::MinStackDefault);

    ThreadContext* threadContext = requestContext->GetThreadContext();

    if (!this->EnsureInitialized(requestContext))
    {
        return FALSE;
    }

    Js::RecyclableObject *targetObj = this;
    CustomExternalWrapperType * type = this->GetExternalType();
    Js::JavascriptFunction* setMethod = nullptr;
    if (type->GetJsGetterSetterInterceptor()->setTrap != nullptr)
    {
        setMethod = Js::VarTo<JavascriptFunction>(type->GetJsGetterSetterInterceptor()->setTrap);
    }
    else
    {
        return fn();
    }

    Assert(!GetScriptContext()->IsHeapEnumInProgress());
    Js::Var isPropertyNameNumeric;
    Js::Var propertyNameNumericValue;
    Js::Var propertyName = getPropertyName(requestContext, &isPropertyNameNumeric, &propertyNameNumericValue);

    PropertyValueInfo::SetNoCache(info, targetObj);
    PropertyValueInfo::DisablePrototypeCache(info, targetObj);
    if (nullptr != setMethod)
    {
        Js::Var setPropertyResult = threadContext->ExecuteImplicitCall(setMethod, Js::ImplicitCall_Accessor, [=]()->Js::Var
        {
            return CALL_FUNCTION(threadContext, setMethod, Js::CallInfo(Js::CallFlags_Value, 5), targetObj, propertyName, isPropertyNameNumeric, propertyNameNumericValue, newValue);
        });

        BOOL setResult = Js::JavascriptConversion::ToBoolean(setPropertyResult, requestContext);
        return setResult;
    }

    return FALSE;
}

Js::PropertyQueryFlags CustomExternalWrapperObject::GetPropertyQuery(Js::Var originalInstance, Js::PropertyId propertyId, Js::Var* value, Js::PropertyValueInfo* info, Js::ScriptContext* requestContext)
{
    if (!this->VerifyObjectAlive()) return Js::PropertyQueryFlags::Property_NotFound;
    Js::PropertyDescriptor result;

    auto fn = [&](Js::RecyclableObject* object)-> BOOL {
        BOOL found = JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext));
        result.SetValue(*value);
        return found;
    };
    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        return GetName(requestContext, propertyId, isPropertyNameNumeric, propertyNameNumericValue);
    };
    BOOL foundProperty = GetPropertyTrap(originalInstance, &result, fn, getPropertyName, requestContext, info);
    if (!foundProperty)
    {
        *value = requestContext->GetMissingPropertyResult();
    }
    else
    {
        *value = GetValueFromDescriptor(originalInstance, result, requestContext);
    }
    return Js::JavascriptConversion::BooleanToPropertyQueryFlags(foundProperty);
}

Js::PropertyQueryFlags CustomExternalWrapperObject::GetPropertyQuery(Js::Var originalInstance, Js::JavascriptString* propertyNameString, Js::Var* value, Js::PropertyValueInfo* info, Js::ScriptContext* requestContext)
{
    if (!this->VerifyObjectAlive()) return Js::PropertyQueryFlags::Property_NotFound;
    Js::PropertyDescriptor result;

    auto fn = [&](Js::RecyclableObject* object)-> BOOL {
        BOOL found = JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetPropertyQuery(originalInstance, propertyNameString, value, info, requestContext));
        result.SetValue(*value);
        return found;
    };

    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        const Js::PropertyRecord* propertyRecord;
        propertyNameString->GetPropertyRecord(&propertyRecord);
        return GetName(requestContext, propertyRecord->GetPropertyId(), isPropertyNameNumeric, propertyNameNumericValue);
    };

    BOOL foundProperty = GetPropertyTrap(originalInstance, &result, fn, getPropertyName, requestContext, info);
    if (!foundProperty)
    {
        *value = requestContext->GetMissingPropertyResult();
    }
    else
    {
        *value = GetValueFromDescriptor(originalInstance, result, requestContext);
    }
    return Js::JavascriptConversion::BooleanToPropertyQueryFlags(foundProperty);
}

Js::PropertyQueryFlags CustomExternalWrapperObject::GetPropertyReferenceQuery(Js::Var originalInstance, Js::PropertyId propertyId, Js::Var* value, Js::PropertyValueInfo* info, Js::ScriptContext* requestContext)
{
    if (!this->VerifyObjectAlive()) return Js::PropertyQueryFlags::Property_NotFound;
    Js::PropertyDescriptor result;

    auto fn = [&](Js::RecyclableObject* object)-> BOOL {
        BOOL found = JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetPropertyReferenceQuery(originalInstance, propertyId, value, info, requestContext));
        result.SetValue(*value);
        return found;
    };

    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        return GetName(requestContext, propertyId, isPropertyNameNumeric, propertyNameNumericValue);
    };

    BOOL foundProperty = GetPropertyTrap(originalInstance, &result, fn, getPropertyName, requestContext, info);
    if (!foundProperty)
    {
        *value = requestContext->GetMissingPropertyResult();
    }
    else
    {
        *value = GetValueFromDescriptor(originalInstance, result, requestContext);
    }
    return Js::JavascriptConversion::BooleanToPropertyQueryFlags(foundProperty);
}

Js::PropertyQueryFlags CustomExternalWrapperObject::HasItemQuery(uint32 index)
{
    auto fn = [&](Js::RecyclableObject* object)-> BOOL {
        return JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::HasItemQuery(index));
    };

    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        *isPropertyNameNumeric = requestContext->GetLibrary()->GetTrue();
        *propertyNameNumericValue = JavascriptNumber::ToVar(index, requestContext);
        return nullptr;
    };

    return Js::JavascriptConversion::BooleanToPropertyQueryFlags(HasPropertyTrap(fn, getPropertyName, nullptr));
}

BOOL CustomExternalWrapperObject::HasOwnItem(uint32 index)
{
    auto fn = [&](Js::RecyclableObject* object)-> BOOL {
        return DynamicObject::HasOwnItem(index);
    };
    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        *isPropertyNameNumeric = requestContext->GetLibrary()->GetTrue();
        *propertyNameNumericValue = JavascriptNumber::ToVar(index, requestContext);
        return nullptr;
    };

    return HasPropertyTrap(fn, getPropertyName, nullptr);
}

Js::PropertyQueryFlags CustomExternalWrapperObject::GetItemQuery(Js::Var originalInstance, uint32 index, Js::Var* value, Js::ScriptContext * requestContext)
{
    if (!this->VerifyObjectAlive()) return Js::PropertyQueryFlags::Property_NotFound;
    Js::PropertyDescriptor result;

    auto fn = [&](Js::RecyclableObject* object)-> BOOL {
        BOOL found = JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetItemQuery(originalInstance, index, value, requestContext));
        result.SetValue(*value);
        return found;
    };

    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        *isPropertyNameNumeric = requestContext->GetLibrary()->GetTrue();
        *propertyNameNumericValue = JavascriptNumber::ToVar(index, requestContext);
        return nullptr;
    };

    BOOL foundProperty = GetPropertyTrap(originalInstance, &result, fn, getPropertyName, requestContext, nullptr);
    if (!foundProperty)
    {
        *value = requestContext->GetMissingItemResult();
    }
    else
    {
        *value = GetValueFromDescriptor(originalInstance, result, requestContext);
    }
    return Js::JavascriptConversion::BooleanToPropertyQueryFlags(foundProperty);
}

Js::PropertyQueryFlags CustomExternalWrapperObject::GetItemReferenceQuery(Js::Var originalInstance, uint32 index, Js::Var* value, Js::ScriptContext * requestContext)
{
    if (!this->VerifyObjectAlive()) return Js::PropertyQueryFlags::Property_NotFound;

    Js::PropertyDescriptor result;
    auto fn = [&](Js::RecyclableObject* object)-> BOOL {
        BOOL found = JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetItemReferenceQuery(originalInstance, index, value, requestContext));
        result.SetValue(*value);
        return found;
    };

    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        *isPropertyNameNumeric = requestContext->GetLibrary()->GetTrue();
        *propertyNameNumericValue = JavascriptNumber::ToVar(index, requestContext);
        return nullptr;
    };

    BOOL foundProperty = GetPropertyTrap(originalInstance, &result, fn, getPropertyName, requestContext, nullptr);
    if (!foundProperty)
    {
        *value = requestContext->GetMissingItemResult();
    }
    else
    {
        *value = GetValueFromDescriptor(originalInstance, result, requestContext);
    }
    return Js::JavascriptConversion::BooleanToPropertyQueryFlags(foundProperty);
}

BOOL CustomExternalWrapperObject::SetItem(uint32 index, Js::Var value, Js::PropertyOperationFlags flags)
{
    if (!this->VerifyObjectAlive()) return FALSE;

    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        *isPropertyNameNumeric = requestContext->GetLibrary()->GetTrue();
        *propertyNameNumericValue = JavascriptNumber::ToVar(index, requestContext);
        return nullptr;
    };

    auto fn = [&]()->BOOL
    {
        // setting the value could be deferred and now we are on a different context from
        // make sure the value has the same context as the containing object.
        value = Js::CrossSite::MarshalVar(this->GetScriptContext(), value);
        return DynamicObject::SetItem(index, value, flags);
    };
    BOOL trapResult = SetPropertyTrap(this, CustomExternalWrapperObject::SetPropertyTrapKind::SetItemKind, getPropertyName, value, GetScriptContext(), flags, FALSE, fn, nullptr);
    if (!trapResult)
    {
        // setting the value could be deferred and now we are on a different context from
        // make sure the value has the same context as the containing object.
        value = Js::CrossSite::MarshalVar(this->GetScriptContext(), value);
        return Js::DynamicObject::SetItem(index, value, flags);
    }

    return TRUE;
}

BOOL CustomExternalWrapperObject::DeleteItem(uint32 index, Js::PropertyOperationFlags flags)
{
    if (!this->VerifyObjectAlive()) return FALSE;
    const PropertyRecord* propertyRecord = nullptr;
    PropertyIdFromInt(index, &propertyRecord);
    return this->DeleteProperty(propertyRecord->GetPropertyId(), flags);
}

BOOL CustomExternalWrapperObject::GetEnumerator(Js::JavascriptStaticEnumerator * enumerator, Js::EnumeratorFlags flags, Js::ScriptContext* requestContext, Js::EnumeratorCache * enumeratorCache)
{
    if (!this->VerifyObjectAlive()) return FALSE;
    ThreadContext* threadContext = requestContext->GetThreadContext();

    if (!this->EnsureInitialized(requestContext))
    {
        return FALSE;
    }

    CustomExternalWrapperType * type = this->GetExternalType();
    Js::JavascriptFunction* getEnumeratorMethod = nullptr;
    if (type->GetJsGetterSetterInterceptor()->enumerateTrap != nullptr)
    {
        getEnumeratorMethod = Js::VarTo<JavascriptFunction>(type->GetJsGetterSetterInterceptor()->enumerateTrap);
    }

    Js::RecyclableObject * targetObj = this;
    JavascriptArray * trapResult = nullptr;
    if (getEnumeratorMethod == nullptr)
    {
        return DynamicObject::GetEnumerator(enumerator, flags, requestContext, enumeratorCache);
    }
    else
    {
        Js::Var getGetEnumeratorResult = threadContext->ExecuteImplicitCall(getEnumeratorMethod, Js::ImplicitCall_Accessor, [=]()->Js::Var
        {
            return CALL_FUNCTION(threadContext, getEnumeratorMethod, Js::CallInfo(Js::CallFlags_Value, 1), targetObj);
        });

        Js::TypeId getResultTypeId = Js::JavascriptOperators::GetTypeId(getGetEnumeratorResult);
        if (getResultTypeId == Js::TypeIds_Undefined)
        {
            return FALSE;
        }

        trapResult = JavascriptOperators::TryFromVar<JavascriptArray>(getGetEnumeratorResult);
        if (trapResult == nullptr)
        {
            return FALSE;
        }
    }

    struct WrapperOwnKeysEnumerator : public JavascriptEnumerator
    {
        typedef JsUtil::BaseHashSet<JsUtil::CharacterBuffer<WCHAR>, Recycler> VisitedNamesHashSet;
        Field(VisitedNamesHashSet*) visited;
        Field(JavascriptArray*) trapResult;
        Field(CustomExternalWrapperObject*) wrapper;
        FieldNoBarrier(ScriptContext*) scriptContext;
        Field(uint32) index;

        DEFINE_VTABLE_CTOR_ABSTRACT(WrapperOwnKeysEnumerator, JavascriptEnumerator)

            WrapperOwnKeysEnumerator(ScriptContext* scriptContext, CustomExternalWrapperObject* wrapper, JavascriptArray* trapResult)
            :JavascriptEnumerator(scriptContext), scriptContext(scriptContext), wrapper(wrapper), trapResult(trapResult)
        {
            visited = RecyclerNew(scriptContext->GetRecycler(), VisitedNamesHashSet, scriptContext->GetRecycler());
        }
        virtual void Reset() override
        {
            index = 0;
            visited->Reset();
        }

        virtual JavascriptString * MoveAndGetNext(PropertyId& propertyId, PropertyAttributes* attributes = nullptr) override
        {
            propertyId = Constants::NoProperty;
            if (attributes != nullptr)
            {
                *attributes = PropertyEnumerable;
            }
            // 13.7.5.15 EnumerateObjectProperties(O) (https://tc39.github.io/ecma262/#sec-enumerate-object-properties)
            // for (let key of Reflect.ownKeys(obj)) {
            if (trapResult != nullptr)
            {
                uint32 len = trapResult->GetLength();
                while (index < len)
                {
                    Var var = trapResult->DirectGetItem(index++);
                    if (var)
                    {
                        // if (typeof key === "string") {
                        if (VarIs<JavascriptString>(var))
                        {
                            JavascriptString* propertyName = VarTo<JavascriptString>(var);
                            // let desc = Reflect.getOwnPropertyDescriptor(obj, key);
                            Js::PropertyDescriptor desc;
                            BOOL ret = JavascriptOperators::GetOwnPropertyDescriptor(wrapper, propertyName, scriptContext, &desc);
                            const JsUtil::CharacterBuffer<WCHAR> propertyString(propertyName->GetString(), propertyName->GetLength());
                            // if (desc && !visited.has(key)) {
                            if (ret && !visited->Contains(propertyString))
                            {
                                visited->Add(propertyString);
                                // if (desc.enumerable) yield key;
                                if (desc.IsEnumerable())
                                {
                                    //TODO: (vsadov) not sure if should marshal here, it is "getting"
                                    return VarTo<JavascriptString>(CrossSite::MarshalVar(
                                        scriptContext, propertyName, propertyName->GetScriptContext()));
                                }
                            }
                        }
                    }
                }
            }
            return nullptr;
        }
    };

    WrapperOwnKeysEnumerator* ownKeysEnum = RecyclerNew(requestContext->GetRecycler(), WrapperOwnKeysEnumerator, requestContext, this, trapResult);

    return enumerator->Initialize(ownKeysEnum, nullptr, nullptr, flags, requestContext, enumeratorCache);
}

BOOL CustomExternalWrapperObject::SetProperty(Js::PropertyId propertyId, Js::Var value, Js::PropertyOperationFlags flags, Js::PropertyValueInfo* info)
{
    if (!this->VerifyObjectAlive()) return FALSE;
    PROBE_STACK(GetScriptContext(), Js::Constants::MinStackDefault);

    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        return GetName(requestContext, propertyId, isPropertyNameNumeric, propertyNameNumericValue);
    };

    auto fn = [&]()->BOOL
    {
        // setting the value could be deferred and now we are on a different context from
        // make sure the value has the same context as the containing object.
        value = Js::CrossSite::MarshalVar(this->GetScriptContext(), value);
        return DynamicObject::SetProperty(propertyId, value, flags, info);
    };

    return SetPropertyTrap(this, CustomExternalWrapperObject::SetPropertyTrapKind::SetPropertyKind, getPropertyName, value, GetScriptContext(), flags, FALSE, fn, info);
}

BOOL CustomExternalWrapperObject::SetProperty(Js::JavascriptString* propertyNameString, Js::Var value, Js::PropertyOperationFlags flags, Js::PropertyValueInfo* info)
{
    if (!this->VerifyObjectAlive()) return FALSE;
    PROBE_STACK(GetScriptContext(), Js::Constants::MinStackDefault);

    auto getPropertyName = [&](Js::ScriptContext * requestContext, Js::Var * isPropertyNameNumeric, Js::Var * propertyNameNumericValue)->Js::Var
    {
        return propertyNameString;
    };

    auto fn = [&]()->BOOL
    {
        // setting the value could be deferred and now we are on a different context from
        // make sure the value has the same context as the containing object.
        value = Js::CrossSite::MarshalVar(this->GetScriptContext(), value);
        return DynamicObject::SetProperty(propertyNameString, value, flags, info);
    };

    return SetPropertyTrap(this, CustomExternalWrapperObject::SetPropertyTrapKind::SetPropertyKind, getPropertyName, value, GetScriptContext(), flags, FALSE, fn, info);
}

BOOL CustomExternalWrapperObject::EnsureNoRedeclProperty(Js::PropertyId propertyId)
{
    CustomExternalWrapperObject::HasOwnPropertyCheckNoRedecl(propertyId);
    return true;
}

BOOL CustomExternalWrapperObject::InitProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info)
{
    if (!this->VerifyObjectAlive()) return FALSE;

    ThreadContext* threadContext = GetScriptContext()->GetThreadContext();

    // Caller does not pass requestContext. Retrieve from host scriptContext stack.
    Js::ScriptContext* requestContext =
        threadContext->GetPreviousHostScriptContext()->GetScriptContext();

    if (!this->EnsureInitialized(requestContext))
    {
        return FALSE;
    }

    // setting the value could be deferred and now we are on a different context from
    // make sure the value has the same context as the containing object.
    value = Js::CrossSite::MarshalVar(this->GetScriptContext(), value);
    return DynamicObject::InitProperty(propertyId, value, flags, info);
}

BOOL CustomExternalWrapperObject::DeleteProperty(Js::PropertyId propertyId, Js::PropertyOperationFlags flags)
{
    if (!this->VerifyObjectAlive()) return FALSE;

    PROBE_STACK(GetScriptContext(), Js::Constants::MinStackDefault);

    ThreadContext* threadContext = GetScriptContext()->GetThreadContext();

    // Caller does not pass requestContext. Retrieve from host scriptContext stack.
    Js::ScriptContext* requestContext =
        threadContext->GetPreviousHostScriptContext()->GetScriptContext();

    if (!this->EnsureInitialized(requestContext))
    {
        return FALSE;
    }

    RecyclableObject * targetObj = this;
    CustomExternalWrapperType * type = this->GetExternalType();
    Js::JavascriptFunction* deleteMethod = nullptr;
    if (type->GetJsGetterSetterInterceptor()->deletePropertyTrap != nullptr)
    {
        deleteMethod = Js::VarTo<JavascriptFunction>(type->GetJsGetterSetterInterceptor()->deletePropertyTrap);
    }

    Assert(!GetScriptContext()->IsHeapEnumInProgress());
    if (nullptr == deleteMethod)
    {
        return __super::DeleteProperty(propertyId, flags);
    }

    Js::Var isPropertyNameNumeric;
    Js::Var propertyNameNumericValue;
    Js::Var propertyName = GetName(requestContext, propertyId, &isPropertyNameNumeric, &propertyNameNumericValue);

    Js::Var deletePropertyResult = threadContext->ExecuteImplicitCall(deleteMethod, Js::ImplicitCall_Accessor, [=]()->Js::Var
    {
        return CALL_FUNCTION(threadContext, deleteMethod, Js::CallInfo(Js::CallFlags_Value, 4), targetObj, propertyName, isPropertyNameNumeric, propertyNameNumericValue);
    });

    BOOL trapResult = Js::JavascriptConversion::ToBoolean(deletePropertyResult, requestContext);
    if (!trapResult)
    {
        if (flags & Js::PropertyOperation_StrictMode)
        {
            Js::JavascriptError::ThrowTypeError(requestContext, JSERR_InconsistentTrapResult, _u("deleteProperty"));
        }
        return trapResult;
    }

    Js::PropertyDescriptor targetPropertyDescriptor;
    if (!Js::JavascriptOperators::GetOwnPropertyDescriptor(targetObj, propertyId, requestContext, &targetPropertyDescriptor))
    {
        return TRUE;
    }
    if (!targetPropertyDescriptor.IsConfigurable())
    {
        Js::JavascriptError::ThrowTypeError(requestContext, JSERR_InconsistentTrapResult, _u("deleteProperty"));
    }
    return TRUE;
}

BOOL CustomExternalWrapperObject::DeleteProperty(Js::JavascriptString *propertyNameString, Js::PropertyOperationFlags flags)
{
    if (!this->VerifyObjectAlive()) return FALSE;
    Js::PropertyRecord const *propertyRecord = nullptr;
    if (Js::JavascriptOperators::ShouldTryDeleteProperty(this, propertyNameString, &propertyRecord))
    {
        Assert(propertyRecord);
        return DeleteProperty(propertyRecord->GetPropertyId(), flags);
    }

    return TRUE;
}

#if ENABLE_TTD
TTD::NSSnapObjects::SnapObjectType CustomExternalWrapperObject::GetSnapTag_TTD() const
{
    //TODO:akatti: TTD. Do we need to define a new TTD object type for CustomExternalWrapperObject?
    return TTD::NSSnapObjects::SnapObjectType::SnapExternalObject;
}

void CustomExternalWrapperObject::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
{
    TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<void*, TTD::NSSnapObjects::SnapObjectType::SnapExternalObject>(objData, nullptr);
}
#endif
#endif // _CHAKRACOREBUILD
