6#include <Ultralight/CAPI/CAPI_JSClass.h>
9#include <Ultralight/js/detail/ClassBinding.h>
36template <
typename Signature,
typename C>
37constexpr auto Overload(Signature C::* member) {
54struct TypeTraits<T*, std::enable_if_t<std::is_class_v<T>>> {
56 return detail::UnwrapInstance<T>(ctx, value, out, exception);
59 return detail::WrapView(ctx, value);
61 static constexpr const char*
SchemaType() {
return detail::ClassSchemaName<T>(); }
82struct TypeTraits<H,
std::enable_if_t<detail::kIsCanonicalHolder<H>>> {
85 using T =
typename Traits::element_type;
88 static bool FromJS(
ULJSContext ctx, ULJSValue value, H* out, ULJSValue* exception) {
90 "an exclusive holder cannot be a parameter: JavaScript cannot relinquish "
91 "ownership through an argument. Borrow with T*, or take ownership "
92 "explicitly with js::Detach<T>()");
93 T* instance =
nullptr;
94 if (!detail::UnwrapInstance<T>(ctx, value, &instance, exception))
100 if constexpr (std::is_same_v<H, RefPtr<T>> || std::is_same_v<H, Eternal<T>>) {
107 void* raw = ulJSObjectGetInstanceHolder(value, detail::BoundClass<T>());
108 if (!raw ||
static_cast<detail::StakeBox<H>*
>(raw)->magic != detail::kStakeBoxMagic) {
110 std::string name = detail::ClassSchemaName<T>();
111 std::string message =
"this " + name
112 +
" has no shared owner this parameter can copy (declare the parameter as "
113 + name +
"* to borrow the instance)";
118 *out =
static_cast<detail::StakeBox<H>*
>(raw)->holder;
125 return detail::WrapOwned<H>(ctx, std::move(
const_cast<H&
>(value)));
127 return detail::WrapOwned<H>(ctx, H(value));
130 static constexpr const char* SchemaType() {
return detail::ClassSchemaName<T>(); }
285 : api_(other.api_), cls_(other.cls_), path_(std::move(other.path_)),
286 full_path_(std::move(other.full_path_)), shapes_(std::move(other.shapes_)),
287 ctor_(std::move(other.ctor_)), methods_(std::move(other.methods_)),
288 properties_(std::move(other.properties_)), statics_(std::move(other.statics_)) {
289 other.api_ =
nullptr;
296 if (api_ && cls_ && ulJSAPIRegisterClass(api_, path_.c_str(), cls_))
297 EmitShapeEnrichment();
312 template <
typename... Args>
314 (detail::ValidateParam<Args>(), ...);
315 static_assert(std::is_constructible_v<std::remove_cv_t<T>, detail::ParamStorage<Args>&&...>,
316 "T is not constructible from these argument types");
318 auto* bound =
new detail::ClassCtor<T, Args...> { full_path_ };
319 if (ulJSClassSetConstructor(cls_, &detail::ClassCtor<T, Args...>::Invoke, bound,
320 &detail::DestroyPayload<detail::ClassCtor<T, Args...>>))
321 ctor_ = detail::BuildCallableFragment<std::tuple<Args...>, void,
true>(shapes_);
343 template <
typename M,
typename... Anns>
344 requires(std::is_member_function_pointer_v<M> && (detail::Annotation<Anns> && ...))
346 using Traits = detail::MemberFnTraits<M>;
347 static_assert(std::is_base_of_v<typename Traits::Class, T>,
348 "the member pointer must belong to T (or a base of T)");
349 using Shape = detail::ParamShape<typename Traits::Params>;
350 [&]<
size_t... Is>(std::index_sequence<Is...>) {
351 (detail::ValidateParam<std::tuple_element_t<Is, typename Shape::Converted>>(), ...);
352 }(std::make_index_sequence<Shape::kCount> {});
353 constexpr size_t kNames = detail::kArgAnnotationCount<Anns...>;
354 static_assert(kNames == 0 || kNames == Shape::kCount,
355 "js::Param annotations must name every payload parameter or none");
356 static_assert(detail::kDocAnnotationCount<Anns...> <= 1,
357 "at most one js::Doc annotation per registration");
358 detail::AnnotationSet set = detail::CollectAnnotations(annotations...);
361 =
new detail::BoundClassMethod<T, M> { method, MemberPath(name), {}, {} };
363 = bound->path + detail::RenderSignature<typename Shape::Converted>(set);
364 bound->names.assign(set.names, set.names + set.name_count);
365 if (!ulJSClassAddMethod(cls_, name, &detail::ClassMethodTrampoline<T, M>, bound,
366 &detail::DestroyPayload<detail::BoundClassMethod<T, M>>,
367 kULJSPropertyAttributes_DontEnum))
369 using R =
typename Traits::Return;
370 methods_[name ? name :
""]
371 = detail::BuildCallableFragment<
typename Shape::Converted, R, Shape::kHasResolver,
372 Shape::kHasResolver || detail::kIsAsyncReturn<R>>(
391 template <
typename F>
394 "this field type has no js::TypeTraits: use a supported built-in, make it "
395 "a plain aggregate struct, or specialize js::TypeTraits for it");
396 static_assert(!detail::IsCallScopedView<std::remove_cv_t<F>>::value,
397 "a bound field cannot be a std::span (the instance would store a view "
398 "that dies with the bound call); use std::vector<E> or RefPtr<Buffer>");
400 auto* bound =
new detail::BoundField<T, F> { member, MemberPath(name) };
402 if constexpr (std::is_const_v<F>) {
403 added = ulJSClassAddProperty(cls_, name, &detail::FieldGetterTrampoline<T, F>,
nullptr,
404 bound, &detail::DestroyPayload<detail::BoundField<T, F>>);
406 static_assert(detail::ConvertibleFromJS<detail::ParamStorage<F>>,
407 "this field type converts to JavaScript only; expose it through "
408 "Property() with a getter instead");
409 added = ulJSClassAddProperty(cls_, name, &detail::FieldGetterTrampoline<T, F>,
410 &detail::FieldSetterTrampoline<T, F>, bound,
411 &detail::DestroyPayload<detail::BoundField<T, F>>);
414 properties_[name ? name :
""]
415 = detail::BuildTypeFragment<std::remove_cv_t<F>>(shapes_);
431 template <
typename GM>
432 requires std::is_member_function_pointer_v<GM>
434 return AddAccessor(name, getter,
nullptr);
452 template <
typename GM,
typename SM>
453 requires(std::is_member_function_pointer_v<GM> && std::is_member_function_pointer_v<SM>)
455 return AddAccessor(name, getter, setter);
475 template <
typename Fn,
typename... Anns>
476 requires(detail::Annotation<Anns> && ...)
478 using F = std::decay_t<Fn>;
479 static_assert(detail::IsIntrospectableCallable<F>::value,
480 "a static method must have exactly one non-template call signature "
481 "(generic lambdas cannot be introspected)");
482 using Traits = detail::CallableTraits<F>;
483 using Shape = detail::ParamShape<typename Traits::Params>;
484 [&]<
size_t... Is>(std::index_sequence<Is...>) {
485 (detail::ValidateParam<std::tuple_element_t<Is, typename Shape::Converted>>(), ...);
486 }(std::make_index_sequence<Shape::kCount> {});
487 constexpr size_t kNames = detail::kArgAnnotationCount<Anns...>;
488 static_assert(kNames == 0 || kNames == Shape::kCount,
489 "js::Param annotations must name every payload parameter or none");
490 static_assert(detail::kDocAnnotationCount<Anns...> <= 1,
491 "at most one js::Doc annotation per registration");
492 detail::AnnotationSet set = detail::CollectAnnotations(annotations...);
495 =
new detail::BoundCallable<F> { std::forward<Fn>(fn), MemberPath(name), {}, {} };
497 = bound->path + detail::RenderSignature<typename Shape::Converted>(set);
498 bound->names.assign(set.names, set.names + set.name_count);
499 if constexpr (detail::HasSettableBoundPath<F>)
500 bound->fn.SetBoundPath(bound->path);
501 if (!ulJSClassAddStaticMethod(cls_, name, &detail::ClassStaticTrampoline<F>, bound,
502 &detail::DestroyBoundCallable<F>))
504 using R =
typename detail::CallableTraits<F>::Return;
505 statics_[name ? name :
""]
506 = detail::BuildCallableFragment<
typename Shape::Converted, R, Shape::kHasResolver,
507 Shape::kHasResolver || detail::kIsAsyncReturn<R>>(
536 template <
typename C,
typename M,
typename... Anns>
537 requires(std::is_member_function_pointer_v<M> && !
js::Holder<C>
540 const Anns&... annotations) {
541 static_assert(std::is_base_of_v<typename detail::MemberFnTraits<M>::Class, C>,
542 "the member pointer must belong to the receiver's class (or a base of "
565 template <
typename H,
typename M,
typename... Anns>
567 && std::is_member_function_pointer_v<M> && (detail::Annotation<Anns> && ...))
569 const Anns&... annotations) {
577 : api_(api), cls_(cls), path_(
std::move(path)), full_path_(
std::move(full_path)) {}
579 std::string MemberPath(
const char* name)
const {
580 return full_path_ +
"." + (name ? name :
"");
583 template <
typename GM,
typename SM>
584 ClassBuilder& AddAccessor(
const char* name, GM getter, SM setter) {
585 using GTraits = detail::MemberFnTraits<GM>;
586 static_assert(std::tuple_size_v<typename GTraits::Params> == 0,
587 "a property getter takes no parameters");
588 static_assert(!std::is_void_v<typename GTraits::Return>,
589 "a property getter must return a value");
593 if constexpr (std::is_same_v<SM, std::nullptr_t>) {
594 using Bound = detail::BoundAccessor<T, GM, std::nullptr_t>;
595 auto* bound =
new Bound { getter,
nullptr, MemberPath(name) };
596 added = ulJSClassAddProperty(cls_, name, &detail::AccessorGetterTrampoline<T, Bound>,
597 nullptr, bound, &detail::DestroyPayload<Bound>);
599 using STraits = detail::MemberFnTraits<SM>;
600 static_assert(std::tuple_size_v<typename STraits::Params> == 1,
601 "a property setter takes exactly one parameter");
602 using Bound = detail::BoundAccessor<T, GM, SM>;
603 auto* bound =
new Bound { getter, setter, MemberPath(name) };
604 added = ulJSClassAddProperty(cls_, name, &detail::AccessorGetterTrampoline<T, Bound>,
605 &detail::AccessorSetterTrampoline<T, Bound>, bound,
606 &detail::DestroyPayload<Bound>);
609 properties_[name ? name :
""]
610 = detail::BuildTypeFragment<typename GTraits::Return>(shapes_);
619 void EmitShapeEnrichment() {
620 if (ctor_.empty() && methods_.empty() && properties_.empty() && statics_.empty())
622 std::string shape_name = path_;
623 std::replace(shape_name.begin(), shape_name.end(),
'.',
'_');
624 std::string body =
"{";
626 if (!ctor_.empty()) {
631 auto add_section = [&](
const char* key,
const std::map<std::string, std::string>& members) {
640 bool member_first =
true;
641 for (
const auto& [name, entry] : members) {
644 member_first =
false;
645 detail::WriteJsonEscaped(body, name);
651 add_section(
"methods", methods_);
652 add_section(
"properties", properties_);
653 add_section(
"statics", statics_);
655 std::string fragment =
"{\"types\":{";
656 detail::WriteJsonEscaped(fragment, shape_name);
659 for (
const auto& [name, shape] : shapes_.shapes) {
660 if (name == shape_name)
663 detail::WriteJsonEscaped(fragment, name);
668 ulJSAPISetMetadata(api_,
"", fragment.c_str());
672 ULJSClass cls_ =
nullptr;
674 std::string full_path_;
675 detail::SchemaShapes shapes_;
677 std::map<std::string, std::string> methods_;
678 std::map<std::string, std::string> properties_;
679 std::map<std::string, std::string> statics_;
684 static_assert(std::is_class_v<T>,
"DefineClass binds class types");
685 using Bare = std::remove_cv_t<T>;
686 if (!api_ || !path || !*path)
687 return ClassBuilder<T>(
nullptr,
nullptr, std::string(), std::string());
688 ULJSClass& cls = detail::ClassRegistry<Bare>::cls;
691 const char* name = path;
692 for (
const char* p = path; *p; ++p) {
696 cls = ulCreateJSClass(name);
725 using Bare = std::remove_cv_t<T>;
727 ULJSClass cls = detail::ClassRegistry<Bare>::cls;
733 void* peek = ulJSObjectGetInstanceHolder(
object.raw(), cls);
734 if (peek &&
static_cast<detail::StakeBox<H>*
>(peek)->magic != detail::kStakeBoxMagic)
736 void* holder =
nullptr;
737 ulJSObjectDetachInstance(
object.raw(), cls, &holder);
740 auto* box =
static_cast<detail::StakeBox<H>*
>(holder);
741 H result = std::move(box->holder);
struct C_JSContext * ULJSContext
Opaque handle to a JavaScript execution context (one page's script world in one View).
Definition View.h:25
struct C_JSAPI * ULJSAPI
Opaque handle to a set of native JavaScript bindings.
Definition View.h:32
ClassBuilder< T > DefineClass(const char *path)
Define a native class at a path, so page script can call its methods, read its properties,...
Definition Class.h:683
Builder for exposing a C++ class to JavaScript.
Definition Class.h:277
~ClassBuilder()
Definition Class.h:295
ClassBuilder & Property(const char *name, GM getter)
Expose a read-only property backed by a getter.
Definition Class.h:433
ClassBuilder & Property(const char *name, GM getter, SM setter)
Expose a read-write property backed by a getter and a setter.
Definition Class.h:454
friend class API
Definition Class.h:574
ClassBuilder(ClassBuilder &&other) noexcept
Definition Class.h:284
ClassBuilder & Field(const char *name, F T::*member)
Expose a data member as a property.
Definition Class.h:392
ClassBuilder & operator=(const ClassBuilder &)=delete
ClassBuilder & Constructor()
Declare the constructor's parameter types.
Definition Class.h:313
ClassBuilder(const ClassBuilder &)=delete
typename ClassHolder< std::remove_cv_t< T > >::type HolderType
The class's canonical holder type (see js::ClassHolder).
Definition Class.h:282
ClassBuilder & Method(const char *name, M method, const Anns &... annotations)
Add an instance method.
Definition Class.h:345
ClassBuilder & StaticMethod(const char *name, Fn &&fn, const Anns &... annotations)
Add a static method (a function on the constructor, eg, myApp.Database.open(...)).
Definition Class.h:477
ClassBuilder & operator=(ClassBuilder &&)=delete
ULJSValue ToJS(ULJSContext ctx) const
Convert this error to a JavaScript value.
Definition Error.h:388
static Error TypeError(std::string message)
Create a native TypeError.
Definition Error.h:144
A handle to a live JavaScript value.
Definition Value.h:210
Whether or not H (ignoring const and references) is an owning holder: its ultralight::HolderTraits de...
Definition Holder.h:102
Whether or not H (ignoring const and references) is a weak holder: its ultralight::HolderTraits decla...
Definition Holder.h:113
Whether or not js::TypeTraits can convert T (ignoring const, volatile, and references).
Definition TypeTraits.h:275
Definition StringSTL.h:166
js::Value ToJS(const js::Context &context, const Element &element)
Get an element's JavaScript object (the same object the page's scripts see for it).
Definition JSInterop.h:97
Type-checked JavaScript bridge between C++ and web pages.
Definition JSInterop.h:141
auto Bind(T *instance, M method)
Bind a member function to an instance without writing a lambda.
Definition API.h:1435
constexpr auto Overload(Signature C::*member)
Select one overload of a member function for ClassBuilder::Method().
Definition Class.h:37
ClassHolder< std::remove_cv_t< T > >::type Detach(const Value &object)
Detach a bound-class instance from its wrapper and take ownership back.
Definition Class.h:724
Root namespace for every public Ultralight type, function, and enumeration.
@ Exclusive
One holder owns the instance and ownership moves (eg, std::unique_ptr).
Definition HolderTraits.h:26
@ Shared
Copies share ownership (eg, RefPtr), or the instance is never destroyed (js::Eternal).
Definition HolderTraits.h:20
Traits template that lets the library hold objects through custom smart pointers.
Definition HolderTraits.h:156
Type trait that selects the smart pointer type for owned instances of a bound class.
Definition Holder.h:170
std::conditional_t< std::is_base_of_v< RefCounted, T >, RefPtr< T >, std::unique_ptr< T > > type
Definition Holder.h:171
static ULJSValue ToJS(ULJSContext ctx, T *const &value)
Definition Class.h:58
static constexpr const char * SchemaType()
Definition Class.h:61
static bool FromJS(ULJSContext ctx, ULJSValue value, T **out, ULJSValue *exception)
Definition Class.h:55
Type conversions between C++ and JavaScript across the bridge.
Definition TypeTraits.h:240