diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 0510bc7e0e4bb09049b2622b9eea2a2c0842f3a4..50731ddaeec0c1013729a5843dfc615246c9b49a 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -5570,6 +5570,232 @@ OH_JSVM_DefineClassWithPropertyHandler(JSVM_Env env, return GET_RETURN_STATUS(env); } +JSVM_Status ProcessPropertyHandler(JSVM_Env env, + v8::Local tpl, + JSVM_PropertyHandlerCfg propertyHandlerCfg, + JSVM_Callback callAsFunctionCallback, + v8impl::JSVM_PropertyHandlerCfgStruct** propertyHandlerCfgStruct) { + CHECK_ARG(env, propertyHandlerCfg); + *propertyHandlerCfgStruct = v8impl::CreatePropertyCfg(env, propertyHandlerCfg); + if (*propertyHandlerCfgStruct == nullptr) { + return JSVM_GENERIC_FAILURE; + } + v8::Local cbdata = v8impl::CallbackBundle::New(env, *propertyHandlerCfgStruct); + + // register named property handler + v8::NamedPropertyHandlerConfiguration namedPropertyHandler; + if (propertyHandlerCfg->genericNamedPropertyGetterCallback) { + namedPropertyHandler.getter = v8impl::PropertyCallbackWrapper::NameGetterInvoke; + } + if (propertyHandlerCfg->genericNamedPropertySetterCallback) { + namedPropertyHandler.setter = v8impl::PropertyCallbackWrapper::NameSetterInvoke; + } + if (propertyHandlerCfg->genericNamedPropertyDeleterCallback) { + namedPropertyHandler.deleter = v8impl::PropertyCallbackWrapper::NameDeleterInvoke; + } + if (propertyHandlerCfg->genericNamedPropertyEnumeratorCallback) { + namedPropertyHandler.enumerator = v8impl::PropertyCallbackWrapper::NameEnumeratorInvoke; + } + namedPropertyHandler.data = cbdata; + tpl->InstanceTemplate()->SetHandler(namedPropertyHandler); + + // register indexed property handle + v8::IndexedPropertyHandlerConfiguration indexPropertyHandler; + if (propertyHandlerCfg->genericIndexedPropertyGetterCallback) { + indexPropertyHandler.getter = v8impl::PropertyCallbackWrapper::IndexGetterInvoke; + } + if (propertyHandlerCfg->genericIndexedPropertySetterCallback) { + indexPropertyHandler.setter = v8impl::PropertyCallbackWrapper::IndexSetterInvoke; + } + if (propertyHandlerCfg->genericIndexedPropertyDeleterCallback) { + indexPropertyHandler.deleter = v8impl::PropertyCallbackWrapper::IndexDeleterInvoke; + } + if (propertyHandlerCfg->genericIndexedPropertyEnumeratorCallback) { + indexPropertyHandler.enumerator = v8impl::PropertyCallbackWrapper::IndexEnumeratorInvoke; + } + indexPropertyHandler.data = cbdata; + tpl->InstanceTemplate()->SetHandler(indexPropertyHandler); + + // register call as function + if (callAsFunctionCallback && callAsFunctionCallback->callback) { + v8::Local funcCbdata = v8impl::CallbackBundle::New(env, callAsFunctionCallback); + tpl->InstanceTemplate()->SetCallAsFunctionHandler(v8impl::FunctionCallbackWrapper::Invoke, funcCbdata); + } + return JSVM_OK; +} + +class DefineClassOptionsResolver { + public: + void ProcessOptions(size_t length, JSVM_DefineClassOptions options[], JSVM_Env env, + v8::Local tpl) { + for (int32_t i = 0; i < length; i++) { + if (status != JSVM_OK) { + break; + } + switch(options[i].id) { + case JSVM_DEFINE_CLASS_NORMAL: + break; + case JSVM_DEFINE_CLASS_WITH_COUNT: { + auto count = options[i].content.num; + v8::Local instance_templ = tpl->InstanceTemplate(); + instance_templ->SetInternalFieldCount(count); + break; + } + case JSVM_DEFINE_CLASS_WITH_PROPERTY_HANDLER: { + hasPropertyHandle = true; + auto* propertyHandle = static_cast(options[i].content.ptr); + propertyHandlerCfg = propertyHandle->propertyHandlerCfg; + callAsFunctionCallback = propertyHandle->callAsFunctionCallback; + status = ProcessPropertyHandler(env, tpl, propertyHandlerCfg, callAsFunctionCallback, + &propertyHandlerCfgStruct); + break; + } + default: { + status = JSVM_INVALID_ARG; + } + } + } + } + + JSVM_Status GetStatus() { + return status; + } + + v8impl::JSVM_PropertyHandlerCfgStruct* GetPropertyHandler() { + return propertyHandlerCfgStruct; + } + + bool HasPropertyHandler() { + return hasPropertyHandle; + } + + private: + JSVM_PropertyHandlerCfg propertyHandlerCfg = nullptr; + JSVM_Callback callAsFunctionCallback = nullptr; + bool hasPropertyHandle = false; + JSVM_Status status = JSVM_OK; + v8impl::JSVM_PropertyHandlerCfgStruct* propertyHandlerCfgStruct = nullptr; +}; + +JSVM_Status JSVM_CDECL +OH_JSVM_DefineClassWithOptions(JSVM_Env env, + const char* utf8name, + size_t length, + JSVM_Callback constructor, + size_t propertyCount, + const JSVM_PropertyDescriptor* properties, + JSVM_Value parentClass, + size_t option_count, + JSVM_DefineClassOptions options[], + JSVM_Value* result) { + JSVM_PREAMBLE(env); + CHECK_ARG(env, result); + CHECK_ARG(env, constructor); + CHECK_ARG(env, constructor->callback); + + if (propertyCount > 0) { + CHECK_ARG(env, properties); + } + + v8::Isolate* isolate = env->isolate; + v8::EscapableHandleScope scope(isolate); + v8::Local tpl; + STATUS_CALL(v8impl::FunctionCallbackWrapper::NewTemplate( + env, constructor, &tpl)); + + v8::Local name_string; + CHECK_NEW_FROM_UTF8_LEN(env, name_string, utf8name, length); + tpl->SetClassName(name_string); + + size_t static_property_count = 0; + for (size_t i = 0; i < propertyCount; i++) { + const JSVM_PropertyDescriptor* p = properties + i; + + if ((p->attributes & JSVM_STATIC) != 0) { // attributes + // Static properties are handled separately below. + static_property_count++; + continue; + } + + v8::Local property_name; + STATUS_CALL(v8impl::V8NameFromPropertyDescriptor(env, p, &property_name)); + v8::PropertyAttribute attributes = v8impl::V8PropertyAttributesFromDescriptor(p); + + // This code is similar to that in OH_JSVM_DefineProperties(); the + // difference is it applies to a template instead of an object, + // and preferred PropertyAttribute for lack of PropertyDescriptor + // support on ObjectTemplate. + if (p->getter != nullptr || p->setter != nullptr) { + v8::Local getter_tpl; + v8::Local setter_tpl; + if (p->getter != nullptr) { + STATUS_CALL(v8impl::FunctionCallbackWrapper::NewTemplate( + env, p->getter, &getter_tpl)); + } + if (p->setter != nullptr) { + STATUS_CALL(v8impl::FunctionCallbackWrapper::NewTemplate( + env, p->setter, &setter_tpl)); + } + + tpl->PrototypeTemplate()->SetAccessorProperty(property_name, + getter_tpl, + setter_tpl, + attributes, + v8::AccessControl::DEFAULT); + } else if (p->method != nullptr) { + v8::Local t; + STATUS_CALL(v8impl::FunctionCallbackWrapper::NewTemplate( + env, p->method, &t, v8::Signature::New(isolate, tpl))); + + tpl->PrototypeTemplate()->Set(property_name, t, attributes); + } else { + v8::Local value = v8impl::V8LocalValueFromJsValue(p->value); + tpl->PrototypeTemplate()->Set(property_name, value, attributes); + } + } + + if (parentClass != nullptr) { + v8::Local parentFunc; + CHECK_TO_FUNCTION(env, parentFunc, parentClass); + if (!tpl->Inherit(parentFunc)) { + return JSVM_INVALID_ARG; + } + } + + DefineClassOptionsResolver optionResolver; + optionResolver.ProcessOptions(option_count, options, env, tpl); + + if (optionResolver.GetStatus() != JSVM_OK) { + return optionResolver.GetStatus(); + } + + v8::Local context = env->context(); + *result = v8impl::JsValueFromV8LocalValue( + scope.Escape(tpl->GetFunction(context).ToLocalChecked())); + + if (optionResolver.HasPropertyHandler()) { + v8impl::Reference::New(env, v8impl::V8LocalValueFromJsValue(*result), 0, v8impl::Ownership::kRuntime, + v8impl::CfgFinalizedCallback, optionResolver.GetPropertyHandler(), nullptr); + } + + if (static_property_count > 0) { + std::vector static_descriptors; + static_descriptors.reserve(static_property_count); + + for (size_t i = 0; i < propertyCount; i++) { + const JSVM_PropertyDescriptor* p = properties + i; + if ((p->attributes & JSVM_STATIC) != 0) { + static_descriptors.push_back(*p); + } + } + + STATUS_CALL(OH_JSVM_DefineProperties( + env, *result, static_descriptors.size(), static_descriptors.data())); + } + + return GET_RETURN_STATUS(env); +} + JSVM_Status JSVM_CDECL OH_JSVM_IsLocked(JSVM_Env env, bool* isLocked) { CHECK_ENV(env); diff --git a/src/jsvm.h b/src/jsvm.h index 6723a226cd67f107f54c31f664b03f9382248055..bab044db7258299326a0c378cfaa0317c8c183eb 100644 --- a/src/jsvm.h +++ b/src/jsvm.h @@ -3275,6 +3275,46 @@ JSVM_EXTERN JSVM_Status OH_JSVM_TraceStart(size_t count, const JSVM_TraceCategor * @since 16 */ JSVM_EXTERN JSVM_Status OH_JSVM_TraceStop(JSVM_OutputStream stream, void* streamData); + +/** + * @brief When wrapping a C++ class, the C++ constructor callback passed via constructor + * should be a static method on the class that calls the actual class constructor, then + * wraps the new C++ instance in a JavaScript object according to the different Options + * passed in, and returns the wrapper object. + * + * @param env The environment that the API is invoked under. + * @param utf8name Name of the JavaScript constructor function. For clarity, it is + * recommended to use the C++ class name when wrapping a C++ class. + * @param length The length of the utf8name in bytes, or JSVM_AUTO_LENGTH if it + * is null-terminated. + * @param constructor Struct include callback function that handles constructing instances of the class. + * When wrapping a C++ class, this method must be a static member with the JSVM_Callback.callback + * signature. A C++ class constructor cannot be used. + * Include Optional data to be passed to the constructor callback as the data + * property of the callback info. JSVM_Callback provides more details. + * @param propertyCount Number of items in the properties array argument. + * @param properties Array of property descriptors describing static and instance data + * properties, accessors, and methods on the class See JSVM_PropertyDescriptor. + * @param parentClass The parent-class of the currently defined class. + * @param option_count Number of items in an option array argument. + * @param options DefineClass options to be passed. + * @param result A JSVM_Value representing the constructor function for the class. + * @return Returns JSVM functions result code. + * {@link JSVM_OK } if the function executed successfully. \n + * {@link JSVM_INVALID_ARG } if any of the pointer arguments is NULL. \n + * {@link JSVM_GENERIC_FAILURE} if the input utf8name | constructor | properties is invalid. \n + * @since 16 + */ +JSVM_EXTERN JSVM_Status OH_JSVM_DefineClassWithOptions(JSVM_Env env, + const char* utf8name, + size_t length, + JSVM_Callback constructor, + size_t propertyCount, + const JSVM_PropertyDescriptor* properties, + JSVM_Value parentClass, + size_t option_count, + JSVM_DefineClassOptions options[], + JSVM_Value* result); EXTERN_C_END /** @} */ diff --git a/src/jsvm_types.h b/src/jsvm_types.h index ed2b7d9cdea8379603a794f6fc54cfd103fbc649..b5fa039adb20337b8d48a7ba8117f0a11e29459e 100644 --- a/src/jsvm_types.h +++ b/src/jsvm_types.h @@ -928,6 +928,50 @@ typedef void(JSVM_CDECL* JSVM_HandlerForGC)(JSVM_VM vm, JSVM_GCType gcType, JSVM_GCCallbackFlags flags, void* data); +/** + * @brief The property-handler used to define class. + * + * @since 16 + */ +typedef struct { + /** The instance object triggers the corresponding callback function. */ + JSVM_PropertyHandlerCfg propertyHandlerCfg; + /** Calling an instance object as a function will trigger this callback. */ + JSVM_Callback callAsFunctionCallback; +} JSVM_PropertyHandler; + +/** + * @brief DefineClass options id. + * + * @since 16 + */ +typedef enum { + /** Defining a class in normal mode. */ + JSVM_DEFINE_CLASS_NORMAL, + /** Defining a class with count. */ + JSVM_DEFINE_CLASS_WITH_COUNT, + /** Defining a class with property handler. */ + JSVM_DEFINE_CLASS_WITH_PROPERTY_HANDLER, +} JSVM_DefineClassOptionsId; + +/** + * @brief DefineClass options. + * + * @since 16 + */ +typedef struct { + /** DefineClass options id. */ + JSVM_DefineClassOptionsId id; + /** option content. */ + union { + /** for option value with pointer type. */ + void* ptr; + /** for option value with integer type */ + int num; + /** for option value with bool type */ + bool boolean; + } content; +} JSVM_DefineClassOptions; /** @} */ /**