docs
Loading...
Searching...
No Matches
ClassBuilder< T >

#include <Ultralight/js/Class.h>

Overview

template<typename T>
class ultralight::js::ClassBuilder< T >

Builder for exposing a C++ class to JavaScript.

Calling API::DefineClass() returns a ClassBuilder to bind a C++ class under an API namespace, giving page scripts real objects backed by your C++ instances. Scripts can create instances with new or receive them from bound functions.

Chain member declarations on the builder to define the class:

class Database {
public:
explicit Database(std::string file);
std::string Query(std::string sql);
double timeout = 30;
int page_size() const;
void set_page_size(int bytes);
};
app.DefineClass<Database>("Database")
.Method("query", &Database::Query)
.Field("timeout", &Database::timeout)
.Property("pageSize", &Database::page_size, &Database::set_page_size);
ClassBuilder & Property(const char *name, GM getter)
Expose a read-only property backed by a getter.
Definition Class.h:433
ClassBuilder & Field(const char *name, F T::*member)
Expose a data member as a property.
Definition Class.h:392
ClassBuilder & Constructor()
Declare the constructor's parameter types.
Definition Class.h:313
ClassBuilder & Method(const char *name, M method, const Anns &... annotations)
Add an instance method.
Definition Class.h:345

Page scripts can instantiate the class and call its methods:

const db = new app.Database("save.db");
db.timeout = 60;
const rows = db.query("SELECT * FROM saves");
db.query(42); // TypeError: app.Database.query(string): argument 1:
// expected string, got number

Class Registration

The class registers when the ClassBuilder is destroyed, which usually happens at the end of the statement.

The library registers each C++ class once per process. A later call to API::DefineClass() for the same C++ type attaches the existing definition to another path and can't add or alter members.

If you omit Constructor(), calling new on the page throws a TypeError with code ULJS_NO_CTOR. This lets you require callers to obtain instances through static factory methods bound with StaticMethod() or through functions that return an instance.

Instance Ownership

An instance crosses to JavaScript through either an owning holder or a borrowed pointer:

  • An owned instance uses the canonical holder. The holder type is configured by specializing js::ClassHolder, defaulting to RefPtr<T> for RefCounted types and std::unique_ptr<T> otherwise.
  • A raw pointer borrows the instance. Returning a T* gives the page a wrapper without transferring ownership. You must keep the native object alive while pages can call into it, or detach its wrapper before freeing it.

Functions bound to an API can return either owned instances or borrowed pointers:

class Enemy {
public:
double health = 100;
};
void RegisterEnemyAPI(js::API& api, Enemy& boss) {
api.DefineClass<Enemy>("Enemy").Field("health", &Enemy::health);
// The page owns each new Enemy (std::unique_ptr is the default holder).
api["spawnEnemy"] = [] { return std::make_unique<Enemy>(); };
// A borrow: the page gets `boss`, and you keep ownership.
api["boss"] = [&boss]() -> Enemy* { return &boss; };
}
A global JavaScript namespace for native functions and data.
Definition API.h:398
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

The library releases owned instances on the Renderer's thread during a later Renderer::Update() when their wrapper is garbage-collected or when their page navigates away.

Note
Navigating away releases owned instances even when the page enters the back-forward cache. Calls on those instances after the page is restored throw a TypeError with code ULJS_DETACHED.

Closing an Instance

JavaScript garbage collection runs unpredictably. Any class that manages a scarce resource, such as an open file or network socket, should provide an explicit close method built on js::Detach().

Calling js::Detach() on a wrapper releases the native instance and returns the canonical holder, freeing the instance when that holder is destroyed. Later JavaScript calls on that detached wrapper throw a TypeError with code ULJS_DETACHED.

Implement an explicit close method by detaching the wrapper during a method call:

class Connection {
public:
void Disconnect(js::CallInfo info) {
js::Detach<Connection>(info.this_value().ToValue()); // frees this
// Don't touch members after the Detach.
}
};
void RegisterConnection(js::API& api) {
api.DefineClass<Connection>("Connection")
.Method("disconnect", &Connection::Disconnect);
}
Value ToValue() const
Get a Value that keeps the argument after the callback returns.
Definition Value.h:1185
Details of a call to a bound function: the context, this, and the raw arguments.
Definition CallInfo.h:41
Arg this_value() const
Get the call's this value.
Definition CallInfo.h:67
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

Subsequent calls on the disconnected wrapper fail with a TypeError:

const conn = new app.Connection();
conn.disconnect();
conn.disconnect(); // TypeError with code "ULJS_DETACHED"

Detaching an instance follows specific lifetime rules:

  • Pending async calls delay instance destruction. If an async method on the instance is still running when js::Detach() is called, the wrapper detaches immediately but js::Detach() returns an empty holder. The library keeps the instance alive until that call settles, releasing it during a later Renderer::Update().
  • Borrowed instances must be detached across all pages. Because each page creates its own wrapper for a borrowed instance, you must call js::Detach() on every page's wrapper before freeing the native object.

Ownership Cycles

A native instance that stores a js::Value pointing back to its own JavaScript wrapper creates a reference cycle that prevents garbage collection while the page is alive. To break the cycle, reset the stored handle in an explicit close method, or store a js::WeakValue instead.

See also
js::API::DefineClass(), js::ClassHolder, js::Detach(), js::Eternal, js::Overload()

Public Types

using HolderType = typename ClassHolder<std::remove_cv_t<T>>::type
 The class's canonical holder type (see js::ClassHolder).

Public Member Functions

 ClassBuilder (ClassBuilder &&other) noexcept
 ClassBuilder (const ClassBuilder &)=delete
ClassBuilder & operator= (const ClassBuilder &)=delete
ClassBuilder & operator= (ClassBuilder &&)=delete
 ~ClassBuilder ()
template<typename... Args>
ClassBuilder & Constructor ()
 Declare the constructor's parameter types.
template<typename M, typename... Anns>
requires (std::is_member_function_pointer_v<M> && (detail::Annotation<Anns> && ...))
ClassBuilder & Method (const char *name, M method, const Anns &... annotations)
 Add an instance method.
template<typename F>
ClassBuilder & Field (const char *name, F T::*member)
 Expose a data member as a property.
template<typename GM>
requires std::is_member_function_pointer_v<GM>
ClassBuilder & Property (const char *name, GM getter)
 Expose a read-only property backed by a getter.
template<typename GM, typename SM>
requires (std::is_member_function_pointer_v<GM> && std::is_member_function_pointer_v<SM>)
ClassBuilder & Property (const char *name, GM getter, SM setter)
 Expose a read-write property backed by a getter and a setter.
template<typename Fn, typename... Anns>
requires (detail::Annotation<Anns> && ...)
ClassBuilder & StaticMethod (const char *name, Fn &&fn, const Anns &... annotations)
 Add a static method (a function on the constructor, eg, myApp.Database.open(...)).
template<typename C, typename M, typename... Anns>
requires (std::is_member_function_pointer_v<M> && !js::Holder<C> && !js::LockableHolder<C> && (detail::Annotation<Anns> && ...))
ClassBuilder & StaticMethod (const char *name, C &receiver, M method, const Anns &... annotations)
 Add a static method that calls a member function on an object you own:
template<typename H, typename M, typename... Anns>
requires ((js::Holder<H> || js::LockableHolder<H>) && std::is_member_function_pointer_v<M> && (detail::Annotation<Anns> && ...))
ClassBuilder & StaticMethod (const char *name, H holder, M method, const Anns &... annotations)
 Add a static method that calls a member function through a holder (see js::Bind()).

Friends

class API

Member Typedef Documentation

◆ HolderType

template<typename T>
using HolderType = typename ClassHolder<std::remove_cv_t<T>>::type

The class's canonical holder type (see js::ClassHolder).

Constructor & Destructor Documentation

◆ ClassBuilder() [1/2]

template<typename T>
ClassBuilder ( ClassBuilder< T > && other)
inlinenoexcept

◆ ClassBuilder() [2/2]

template<typename T>
ClassBuilder ( const ClassBuilder< T > & )
delete

◆ ~ClassBuilder()

template<typename T>
~ClassBuilder ( )
inline

Member Function Documentation

◆ Constructor()

template<typename T>
template<typename... Args>
ClassBuilder & Constructor ( )
inline

Declare the constructor's parameter types.

new converts its arguments to these types (throwing a TypeError on a mismatch), then constructs the instance into the class's canonical holder, owned by the new wrapper.

Returns
Returns this builder, so calls chain.
Note
This works only when the canonical holder is RefPtr, std::unique_ptr, or std::shared_ptr. For another holder (js::Eternal included), bind a StaticMethod() that returns the holder instead.

◆ Field()

template<typename T>
template<typename F>
ClassBuilder & Field ( const char * name,
F T::* member )
inline

Expose a data member as a property.

Reads return the member's current value. Assignments convert the value and store it (throwing a TypeError on a mismatch). A const member is read-only.

Parameters
nameThe property's name in JavaScript.
memberA pointer to a data member of T. For a member T inherits, convert the pointer first (eg, static_cast<int Database::*>(&Base::count)).
Returns
Returns this builder, so calls chain.

◆ Method()

template<typename T>
template<typename M, typename... Anns>
requires (std::is_member_function_pointer_v<M> && (detail::Annotation<Anns> && ...))
ClassBuilder & Method ( const char * name,
M method,
const Anns &... annotations )
inline

Add an instance method.

Arguments and the return value convert like those of a function bound with API::Bind(). A method that returns a js::Task or takes a trailing js::Resolver returns a Promise to the page.

Parameters
nameThe method's name in JavaScript.
methodA member function of T (or of a base of T). Select one overload of an overloaded function with js::Overload().
annotationsOptional js::Param / js::Doc annotations for the parameter names and documentation (as on API::Bind()).
Returns
Returns this builder, so calls chain.

◆ operator=() [1/2]

template<typename T>
ClassBuilder & operator= ( ClassBuilder< T > && )
delete

◆ operator=() [2/2]

template<typename T>
ClassBuilder & operator= ( const ClassBuilder< T > & )
delete

◆ Property() [1/2]

template<typename T>
template<typename GM>
requires std::is_member_function_pointer_v<GM>
ClassBuilder & Property ( const char * name,
GM getter )
inline

Expose a read-only property backed by a getter.

Parameters
nameThe property's name in JavaScript.
getterA member function that takes no parameters and returns a convertible type (or a js::Result of one, whose error is thrown).
Returns
Returns this builder, so calls chain.

◆ Property() [2/2]

template<typename T>
template<typename GM, typename SM>
requires (std::is_member_function_pointer_v<GM> && std::is_member_function_pointer_v<SM>)
ClassBuilder & Property ( const char * name,
GM getter,
SM setter )
inline

Expose a read-write property backed by a getter and a setter.

Parameters
nameThe property's name in JavaScript.
getterA member function that takes no parameters and returns a convertible type (or a js::Result of one, whose error is thrown).
setterA member function that takes one convertible parameter and returns void, or a js::Result<void> whose error is thrown to refuse the assignment (eg, a value out of range). Any other return type is a compile error. Assigning a value of the wrong type throws a TypeError.
Returns
Returns this builder, so calls chain.

◆ StaticMethod() [1/3]

template<typename T>
template<typename C, typename M, typename... Anns>
requires (std::is_member_function_pointer_v<M> && !js::Holder<C> && !js::LockableHolder<C> && (detail::Annotation<Anns> && ...))
ClassBuilder & StaticMethod ( const char * name,
C & receiver,
M method,
const Anns &... annotations )
inline

Add a static method that calls a member function on an object you own:

api.DefineClass<Database>("Database")
.StaticMethod("defaultPath", settings, &Settings::DatabasePath);
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
Parameters
nameThe method's name in JavaScript.
receiverThe object the method is called on (borrowed, not copied).
methodA member function of the receiver's class (or of a base of it).
annotationsOptional js::Param / js::Doc annotations for the parameter names and documentation (as on API::Bind()).
Returns
Returns this builder, so calls chain.
Warning
The receiver must stay alive for as long as any page can call the method, even after API::Unbind() (class definitions are never removed). Pass a holder instead (see the overload below) when you can't guarantee that.

◆ StaticMethod() [2/3]

template<typename T>
template<typename Fn, typename... Anns>
requires (detail::Annotation<Anns> && ...)
ClassBuilder & StaticMethod ( const char * name,
Fn && fn,
const Anns &... annotations )
inline

Add a static method (a function on the constructor, eg, myApp.Database.open(...)).

Any callable works, with the same conversions and async forms as API::Bind(). A static method that returns the canonical holder is the usual way to create instances of a class without a Constructor.

Parameters
nameThe method's name in JavaScript.
fnThe callable to bind. It must have exactly one non-template call signature.
annotationsOptional js::Param / js::Doc annotations for the parameter names and documentation (as on API::Bind()).
Returns
Returns this builder, so calls chain.

◆ StaticMethod() [3/3]

template<typename T>
template<typename H, typename M, typename... Anns>
requires ((js::Holder<H> || js::LockableHolder<H>) && std::is_member_function_pointer_v<M> && (detail::Annotation<Anns> && ...))
ClassBuilder & StaticMethod ( const char * name,
H holder,
M method,
const Anns &... annotations )
inline

Add a static method that calls a member function through a holder (see js::Bind()).

A shared holder keeps the receiver alive. A weak holder is locked for each call, and a call after the receiver is gone throws a TypeError with code ULJS_DETACHED.

Parameters
nameThe method's name in JavaScript.
holderA shared or weak holder of the receiver (std::unique_ptr isn't accepted).
methodA member function of the holder's element type (or of a base of it).
annotationsOptional js::Param / js::Doc annotations for the parameter names and documentation (as on API::Bind()).
Returns
Returns this builder, so calls chain.

◆ API

template<typename T>
friend class API
friend

The documentation for this class was generated from the following files: