mirror of
https://github.com/lexmount/moli.git
synced 2026-10-02 16:00:44 +00:00
306 lines
18 KiB
Rust
306 lines
18 KiB
Rust
//! Declarative Web API object and interface helpers.
|
|
//!
|
|
//! This crate is the Rust declaration layer that future WebIDL codegen can
|
|
//! target. It intentionally keeps conversion/parsing concerns in
|
|
//! `moli-webidl` and focuses on constructing V8 interface objects,
|
|
//! prototypes, and branded object instances.
|
|
//!
|
|
//! # Object declaration model
|
|
//!
|
|
//! A `#[derive(WebApiObject)]` struct describes the JavaScript surface that must
|
|
//! be installed on a V8 object. Field annotations decide whether a Rust field is
|
|
//! installed:
|
|
//!
|
|
//! - `#[webapi(data_property)]` defines a JavaScript own data property. It is
|
|
//! non-enumerable by default and can opt into `enumerable`, `readonly`, or
|
|
//! `dont_delete`. Rust slices, arrays, and vectors whose items implement
|
|
//! `WebApiValue` are converted into V8 arrays, so fixed-shape object records
|
|
//! can declare array-valued fields without hand-building `v8::Array`.
|
|
//! - `#[webapi(constant)]` defines a WebIDL constant own property. Constants
|
|
//! are enumerable, read-only, and non-configurable, and must provide
|
|
//! `value = ...`. The declaration installs the same descriptor shape used by
|
|
//! WebIDL constants on constructors and prototypes.
|
|
//! - `#[webapi(method)]` defines a JavaScript own function property. Its
|
|
//! `callback = ...` is the V8 callback, and optional `data = ...` is evaluated
|
|
//! while binding the object so callbacks can receive precomputed state.
|
|
//! Methods are non-enumerable, writable, and configurable by default; use
|
|
//! `enumerable`, `readonly`, or `dont_delete` when the target Web API requires
|
|
//! a different value descriptor.
|
|
//! `symbol = "iterator"`, `symbol = "asyncIterator"`, and
|
|
//! `symbol = "toStringTag"` use the matching well-known symbols as the
|
|
//! property key instead of deriving a string key from the Rust field name. A
|
|
//! method field typed as `Option<()>` is installed
|
|
//! only when it is `Some(())`; this is intended for runtime-selected fixed own
|
|
//! methods whose descriptors still belong to the declaration when present.
|
|
//! - `#[webapi(alias = "sourceName")]` defines an own data property whose value
|
|
//! is copied from another already-declared own property on the same object.
|
|
//! This is useful when Web APIs expose the same function through multiple
|
|
//! keys, such as a string-named method and a well-known symbol, and JavaScript
|
|
//! must observe the two properties as the exact same function object.
|
|
//! - `#[webapi(accessor_property)]` defines a JavaScript accessor property. Its
|
|
//! `getter = ...` and optional `setter = ...` are V8 function callbacks, and
|
|
//! optional `data = ...` follows the same callback-data model as declared
|
|
//! methods. Accessors with different setter callback data can use
|
|
//! `setter_data = ...`; otherwise the setter receives the same data as the
|
|
//! getter. Use `getter_value = ...` instead of `getter = ...` only when the
|
|
//! getter function object is already built or cached by surrounding runtime
|
|
//! code. Accessors also support the same `symbol = ...` keys when the
|
|
//! getter/setter pair represents a symbol-keyed Web API member.
|
|
//! - `#[webapi(native_data_property)]` defines a callback-backed V8 native data
|
|
//! property through `Object::set_native_data_property_with_configuration`.
|
|
//! Its `getter = ...` and optional `setter = ...` are V8 property callbacks,
|
|
//! not JavaScript function callbacks. Reserve this for surfaces whose
|
|
//! semantics intentionally operate on `PropertyCallbackArguments::holder()`.
|
|
//! - `#[webapi(hidden)]` defines a non-enumerable, string-named own property.
|
|
//! This is useful for legacy bookkeeping, but it is still an own property and
|
|
//! can be found by reflection that asks for all own property names.
|
|
//! - `#[webapi(slot)]` stores data in a V8 private slot through
|
|
//! `moli-v8-util`. Use this for internal state that must not appear as a
|
|
//! JavaScript own property.
|
|
//! - `#[webapi(prototype)]` and `#[webapi(to_string_tag)]` install runtime
|
|
//! prototype and `Symbol.toStringTag` metadata from field values.
|
|
//! `to_string_tag` fields are non-enumerable by default and can opt into
|
|
//! `readonly` or `dont_delete` descriptor bits.
|
|
//! - `#[webapi(init = ...)]` can initialize fixed default values without a
|
|
//! Rust value field: `null`, `undefined`, `object`, `null_object`, `array`,
|
|
//! `true`, `false`, `0`, `""`, or `string("...")`. Object-valued defaults
|
|
//! use named string forms such as `init = "array"`; primitive defaults use
|
|
//! literal or typed initializer syntax.
|
|
//!
|
|
//! Field names are converted to WebIDL-style camelCase by default. Use
|
|
//! struct-level `rename_all = "none"` only for declarations that intentionally
|
|
//! expose Rust field spelling. Struct-level `#[webapi(enumerable)]` makes
|
|
//! explicitly declared string-keyed data properties, methods, accessor
|
|
//! properties, native data properties, and aliases enumerable by default. It
|
|
//! can be used with `#[webapi(data_properties)]` for plain objects,
|
|
//! or by itself for WebIDL prototype declarations where every operation and
|
|
//! attribute should inherit WebIDL's enumerable prototype-member default.
|
|
//! Well-known symbol keys remain non-enumerable unless the field itself
|
|
//! declares `enumerable`.
|
|
//!
|
|
//! Fields without one of those installation annotations are declaration-only
|
|
//! inputs. The derive skips them completely, so they do not become properties or
|
|
//! private slots. They can still be referenced by method attributes such as
|
|
//! `data = self.some_field`, which lets a declaration carry callback data
|
|
//! without expanding the object reflection surface.
|
|
//!
|
|
//! Object declarations choose one role:
|
|
//!
|
|
//! - `#[webapi(interface = interfaces::Event)]` creates a native instance. It
|
|
//! receives private identity after its fields initialize successfully, whether
|
|
//! called through `bind`, `bind_into`, or `initialize`. Its default prototype
|
|
//! comes from the descriptor; an explicit `prototype` can preserve another
|
|
//! public shape without changing native identity.
|
|
//! - `#[webapi(plain)]` describes an ordinary unbranded object, defaulting to
|
|
//! `Object.prototype`. Field annotations and `data_properties` still control
|
|
//! which Rust fields become JavaScript properties.
|
|
//! - `#[webapi(fragment)]` installs shared state or members on an existing
|
|
//! target. It assigns no identity and has no implicit prototype. Explicit
|
|
//! prototype or tag declarations are applied by `bind_into`; `initialize`
|
|
//! installs only the declared fields. Fragments never erase native identity.
|
|
//!
|
|
//! Empty declarations need no special opt-in: creating an empty plain object,
|
|
//! branding a native instance and installing prototype metadata all have defined roles.
|
|
//! A single lifetime parameter is inferred as the V8 scope lifetime; use
|
|
//! `scope_lifetime` only when a declaration has multiple lifetimes. Payload slots
|
|
//! hold data, ownership and lifecycle state, not additional boolean brands.
|
|
//! Explicit prototype and tag expressions can use `Type::DESCRIPTOR.name()`
|
|
//! to share names with generated interface metadata.
|
|
//!
|
|
//! Implementation sharing does not imply interface inheritance. For example,
|
|
//! encoding streams share TransformStream internals but have distinct native
|
|
//! interfaces. Their factory assigns the concrete type while the shared state
|
|
//! declaration uses `fragment`. Do not turn actual native instances into plain
|
|
//! objects to make them pass structured clone.
|
|
//!
|
|
//! Declare each interface once with `declare_web_api_interfaces!` and reference
|
|
//! it using `#[webapi(interface = interfaces::ProgressEvent)]`. The generated
|
|
//! descriptor supplies the name and parent to constructor specs, factories and
|
|
//! receiver predicates. Initialization registers the full ancestry, including
|
|
//! when public constructors are absent. Do not repeat `parent` on object state
|
|
//! declarations. A template's optional `name` is only its display name and must
|
|
//! not be used to infer the identity of a shared implementation fragment.
|
|
//!
|
|
//! Identity is independent of realm exposure and mutable JavaScript prototypes.
|
|
//! Use `web_api_object_type` for the primary interface. Native factories can use
|
|
//! `Interface::DESCRIPTOR.initialize(scope, object)`; dynamic factories resolve
|
|
//! their selected name through the same native catalog. JavaScript subclasses
|
|
//! retain the interface implemented by their native constructor, and base
|
|
//! initialization never downgrades a derived object. Inconsistent parents,
|
|
//! cycles and unrelated rebranding fail.
|
|
//!
|
|
//! `WebApiFunctionTemplate` adapts native constructor
|
|
//! callbacks automatically. Hand-written templates can use
|
|
//! `web_api_constructor!(interfaces::Interface, callback)` when construction does not
|
|
//! already go through a named object declaration. Only successful construction
|
|
//! brands the resulting native object; constructors and prototypes are not
|
|
//! instances. A factory implemented with a native Proxy must brand its target
|
|
//! and call `register_web_api_proxy` with its private handler before publication.
|
|
//! The handler belongs to that exact Proxy; author wrappers and revoked Proxies
|
|
//! fail identity checks without invoking traps. Registration grants no clone
|
|
//! capability. Native factories select names from their own descriptors, never
|
|
//! from JavaScript properties such as `new.target.name`. Include detached nodes,
|
|
//! workers, iterators, fallback events, and deserialization paths when adding a
|
|
//! factory. Receiver identity does not replace resource or realm authorization.
|
|
//!
|
|
//! Identity alone grants neither serialization nor transfer capability.
|
|
//! Structured-clone serializers route every identified object through their
|
|
//! explicit codecs and reject unsupported types, including nested instances.
|
|
//! Both message serialization and IndexedDB dispatch on the primary interface,
|
|
//! as required by the [HTML serialization and transfer rules]. A native derived
|
|
//! interface does not implicitly inherit a base interface's codec. The numeric
|
|
//! type IDs are isolate-local and must never be persisted or used as wire tags;
|
|
//! deserializers use their existing wire tags and native construction paths to
|
|
//! assign identity in the receiving isolate.
|
|
//!
|
|
//! [HTML serialization and transfer rules]: https://html.spec.whatwg.org/multipage/structured-data.html#serializable-objects
|
|
//!
|
|
//! The derive generates a Rust-side `new(...)` constructor by default. The
|
|
//! generated constructor takes every non-`()` declaration field as a named
|
|
//! argument and fills `()` declaration fields with `()`. If every field is
|
|
//! `()`, the generated constructor is `new()`. This keeps dynamic state
|
|
//! explicit while removing boilerplate for fixed initialized slots or
|
|
//! accessor/method declaration fields. A field can declare
|
|
//! `#[webapi(constructor_default = expr)]`, or bare
|
|
//! `#[webapi(constructor_default)]` for `Default::default()`, to keep a
|
|
//! Rust-side default out of the generated constructor while still installing
|
|
//! the field normally; this is distinct from `init = ...`, which creates a
|
|
//! JavaScript/V8-side default for a `()` field. Use
|
|
//! `#[webapi(no_dynamic_constructor)]` when the declaration already has a
|
|
//! hand-written constructor with narrower semantics.
|
|
//!
|
|
//! # Receiver checks and Promise-returning members
|
|
//!
|
|
//! `#[webapi(interface = interfaces::Event, receiver)]` checks instance methods
|
|
//! and accessors against the declared interface before argument conversion.
|
|
//! A field can use `receiver = interfaces::Other::is_instance` to select another
|
|
//! interface. Merely naming a template does not enable receiver checks.
|
|
//! Invalid receivers throw `TypeError("Illegal invocation")`. Native subtypes
|
|
//! and cross-realm objects are accepted even after their JavaScript prototypes
|
|
//! change. Forged prototypes, public properties, and author Proxies cannot pass.
|
|
//!
|
|
//! Struct-level `receiver` supplies the default for instance methods and
|
|
//! accessor properties in both derives; a field can override it. Static
|
|
//! methods, data properties, and holder-based native data properties do not
|
|
//! inherit this policy. Already-built `getter_value` functions cannot use it.
|
|
//!
|
|
//! `receiver = path` remains available for a custom native predicate with
|
|
//! signature `fn(&mut v8::PinScope, v8::Local<v8::Object>) -> bool`. It must not
|
|
//! execute JavaScript or throw. Use it for checks that additionally require
|
|
//! native resource state; ordinary interface checks should use their descriptor.
|
|
//!
|
|
//! `returns_promise` on a method (including a static method) or accessor getter
|
|
//! converts synchronous exceptions from both the receiver check and the callback
|
|
//! into rejected Promises in the callback's realm. Successful return values are
|
|
//! unchanged, so cached Promise identity is preserved. An accessor setter still
|
|
//! throws synchronously. Callback data and native function descriptors are
|
|
//! unchanged: these adapters are Rust callbacks, not JavaScript wrappers.
|
|
//!
|
|
//! # Function-template declaration model
|
|
//!
|
|
//! A `#[derive(WebApiFunctionTemplate)]` struct describes a constructor-backed
|
|
//! V8 `FunctionTemplate`, its constructor-template static methods, and its
|
|
//! prototype-template methods. This is the declaration path for bootstrap
|
|
//! surfaces that install onto `FunctionTemplate` and
|
|
//! `FunctionTemplate::prototype_template()` instead of an already-created V8
|
|
//! object. Template methods are non-enumerable by default, and can opt into
|
|
//! `enumerable`, `readonly`, or `dont_delete`; struct-level
|
|
//! `#[webapi(enumerable)]` applies the WebIDL operation default to string-keyed
|
|
//! methods while leaving well-known symbols explicit. Template
|
|
//! `#[webapi(accessor_property)]` creates getter/setter `FunctionTemplate`
|
|
//! values through `ObjectTemplate::set_accessor_property`, so callbacks receive
|
|
//! the actual JavaScript receiver through `FunctionCallbackArguments::this()`.
|
|
//! `#[webapi(native_data_property)]` is the explicit spelling for the
|
|
//! holder-based `ObjectTemplate::set_native_data_property_with_configuration`
|
|
//! path and should be reserved for internal properties that intentionally
|
|
//! operate on the native holder.
|
|
//! `#[webapi(intrinsic_data_property = v8::Intrinsic::...)]` installs a
|
|
//! realm-correct V8 intrinsic directly on the prototype template. This
|
|
//! template-only path avoids reading mutable public builtins such as
|
|
//! `globalThis.Array.prototype` and supports the normal descriptor flags and
|
|
//! well-known symbol keys.
|
|
//! Struct-level
|
|
//! `#[webapi(intrinsic_prototype_parent = v8::Intrinsic::...)]` creates the
|
|
//! hidden parent template required by V8 and links the declared prototype to a
|
|
//! realm-correct intrinsic prototype. This is intended for WebIDL iterator
|
|
//! prototypes and special interfaces such as `DOMException`; it does not read
|
|
//! public constructors such as `globalThis.Error`.
|
|
//! WebIDL iterator templates can pair this with struct-level
|
|
//! `readonly_prototype` and `prototype_to_string_tag = "..."`; the latter
|
|
//! installs the read-only, non-enumerable `Symbol.toStringTag` required by the
|
|
//! iterator prototype algorithms.
|
|
//! `#[webapi(constant)]` installs WebIDL constants on both the constructor
|
|
//! template and prototype template.
|
|
//!
|
|
//! Global interface exposure is deliberately outside either derive. The
|
|
//! renderer's aggregate exposed-interface installer uses V8 lazy data
|
|
//! properties so one complete interface—not an individual declaration
|
|
//! fragment—owns first-read materialization.
|
|
|
|
extern crate self as moli_webapi_declare;
|
|
|
|
mod brand;
|
|
mod callback;
|
|
mod declaration;
|
|
mod error;
|
|
mod interface;
|
|
mod property;
|
|
mod prototype;
|
|
mod value;
|
|
|
|
pub mod __private;
|
|
|
|
pub use interface::WebApiInterfaceDescriptor;
|
|
pub use moli_webapi_declare_derive::{WebApiFunctionTemplate, WebApiObject};
|
|
pub use v8;
|
|
|
|
/// Adapts a manual native constructor to the same identity initialization used
|
|
/// by interface derives. The interface must be a generated native identity type,
|
|
/// never a name obtained from `new.target` or another JavaScript property.
|
|
#[macro_export]
|
|
macro_rules! web_api_constructor {
|
|
($interface:path, $callback:path) => {{
|
|
fn __moli_native_constructor_adapter<'s>(
|
|
scope: &mut $crate::v8::PinScope<'s, '_>,
|
|
args: $crate::v8::FunctionCallbackArguments<'s>,
|
|
rv: $crate::v8::ReturnValue<'s>,
|
|
) {
|
|
$crate::__private::invoke_web_api_constructor(
|
|
scope,
|
|
args,
|
|
rv,
|
|
<$interface>::DESCRIPTOR,
|
|
$callback,
|
|
);
|
|
}
|
|
__moli_native_constructor_adapter
|
|
}};
|
|
}
|
|
|
|
pub use brand::{
|
|
WebApiType, implements_interface, initialize_web_api_object, register_web_api_interfaces,
|
|
register_web_api_proxy, web_api_object_type,
|
|
};
|
|
|
|
pub use declaration::{
|
|
DataPropertyDescriptorDeclaration, ObjectLiteralDeclaration, WebApiFunctionTemplateDeclaration,
|
|
WebApiObjectDeclaration,
|
|
};
|
|
pub use error::BindError;
|
|
pub use property::{
|
|
define_bool_property, define_declared_accessor_property,
|
|
define_declared_accessor_property_by_key, define_declared_constant_property,
|
|
define_declared_data_property, define_declared_data_property_with_attributes,
|
|
define_declared_enumerable_data_property, define_declared_hidden_property,
|
|
define_declared_hidden_property_with_descriptor, define_declared_private_slot,
|
|
define_enumerable_value_property, define_hidden_property, define_number_property,
|
|
define_private_slot, define_string_property, define_value_property,
|
|
illegal_constructor_callback, webidl_constant_property_attributes,
|
|
};
|
|
pub use prototype::{
|
|
define_declared_to_string_tag, define_declared_to_string_tag_with_attributes,
|
|
define_to_string_tag, define_to_string_tag_with_attributes, set_declared_prototype,
|
|
set_interface_prototype, set_required_interface_prototype,
|
|
};
|
|
pub use value::{WebApiTemplateValue, WebApiValue, define_array_data_property};
|