Replace hand-built JS DOM binding plumbing in blitz-vibey-script with typed class layers and a per-instance sized own-data registry - #814
Conversation
…r scheme Port the `Extended<T>` inheritance design from blitz-boa-gui: each DOM interface is an `ExtendLayer` whose own data lives in a per-layer Symbol slot, prototypes are linked via `link_prototype`, and node wrappers are built from their layer chain with `from_chain!`. - Add `shared/` infrastructure: `extends.rs` (Extended<T>/Super/ Constructed/layer chains), error macros, member-definition macros and native-function helpers - Convert Node, CharacterData, Element, Document, Event, CSSStyleDeclaration and ComputedStyle to layers; `new Event()` is now a real constructor and dispatch state (currentTarget, flags) lives in the EventLayer own block - Replace the hand-built prototype objects (DomProtos, init_protos, NodeRef, define_method/define_accessor) with class registration; node_wrapper keeps its identity cache and builds via from_chain!
Replace the per-layer Symbol slots with a single per-instance `OwnDataRegistry`: one `GcRefCell` slot per real layer, addressed by the compile-time `OwnBlock::DEPTH`/`IDX` layout (ported from napi-blitz/crates/napi-inherit). The registry takes over the instance's native data slot; there are no Symbols and no per-layer JS objects. - `with_own`/`with_own_mut`/`set_own_block` become pure Rust-side slot borrows + `TypeId` downcasts and no longer take `&mut Context`; all DOM accessor call sites drop the context argument accordingly - `OwnSlot` carries `Any` downcasting on a blanket impl (not the trait itself) so `dyn OwnSlot: OwnSlot` holds; slot access goes through `Option::as_deref` to reach the trait object directly - taking `&Box<dyn OwnSlot>` would resolve `as_any_ref` to the blanket impl on the box itself and break every downcast - `Extended<T>` keeps only its class-handle role; `own_symbol` and `wrap_own` are gone
|
I am not sure what form Boa will take to enhance the ability to describe inheritance hierarchies on the Rust side, but this approach should facilitate future migration. |
|
There are also some other things here, like |
|
And I'm also not entirely sure if this counts as being somewhat over-engineered. |
The slot list is sized once at attach time and never resized, so a boxed slice carries the same heap layout as a Vec while dropping the capacity field from the registry header. Also tune `#[inline]` placement: drop it from the generic accessors and drivers (monomorphized bodies are already visible to callers) and add it to the non-generic tiny members (`SuperDone::this`, the `RootLayer` chain terminators); `EmitOwn::Chain` now precedes its methods.
- Add the EventTarget layer as Node's parent; add/removeEventListener move there from Node - Add the event class layers (UIEvent, MouseEvent, PointerEvent, WheelEvent, KeyboardEvent, InputEvent) built per DomEventData variant - Split CharacterData/Text/Comment out of node.rs; text/comment wrappers now satisfy instanceof Text/Comment - Give ExtendLayer::build a default 'Failed to construct ...: Illegal constructor' implementation and drop the handwritten ones
- Add DispatchTarget (None / Direct / Callable with cached resolve): event construction no longer materializes target/currentTarget wrappers; they are built lazily on first getter read through the shared wrapper cache - Move per-event dispatch state (target, currentTarget, phase, canceled, stopPropagation flags) into EventLayer's GcRefCell<EventState> block - Store listeners in the EventTargetLayer own block instead of the global node_listeners map, making new EventTarget() a standard, working target; add the standard dispatchEvent method - Drive the DOM chain walk in three phases (capture / target / bubble) with real eventPhase values and reset the transient state afterwards - Report phase-plan-per-receiver via a DispatchStep; keep the vibey-side method bodies (options parsing, once handling, error reporting, on<event> handlers, change synthesis) unchanged - Add tests/events.rs for the event class layers (no DOM involvement, results reported via __blitz_send_message) and DOM-dispatch tests in tests/dom.rs
0386987 to
c3740aa
Compare
blitz-vibey-script DOM bindings onto the Extended<T> layer scheme with sized own-data slotsblitz-vibey-script with typed class layers and a per-instance sized own-data registry
|
Known issue:
Plan: Turn |
|
Known issue: Boa does not yet offer a public weak-reference API, which means every Rust-side structure holding a
Plan: What the cache needs is a strong/weak switching reference: one reference per node whose keep-alive strength follows the node's document membership.
This can only proceed(on Rust-side) after Boa provides a weak reference API. |
Summary
This rewrites the JS DOM class definitions in
blitz-vibey-scriptonto a typed layer ("ExtendLayer") scheme: every DOM interface is a layer whose own data lives in a single per-instance sized-slot registry, prototypes are linked with ES class semantics, and own-block access is a plain Rust-side slot borrow plus aTypeIddowncast. Everything is internal toblitz-vibey-script; the DOM/JS behavior surface is unchanged (covered by the existingdom.rs/preact.rsintegration tests).Design
Layers
Each interface is an
ExtendLayerchained through a compile-timeParenttype, with its own data behind an accessor:Node=Extended<NodeLayer { node_id }>(root layer);CharacterData/Element/DocumentextendNodeLayerEvent=Extended<EventLayer>: configuration (type,target,bubbles,cancelable) and dispatch flags (prevented,stopped,currentTargetviaGcRefCell) live in the own blockCSSStyleDeclaration/ComputedStylecarry the styled node idPrototypes are wired with
link_prototype(child.prototype.__proto__ = parent.prototype,child.constructor.__proto__ = parent.constructor).Node,Document,Element,CharacterDataandEventare registered classes, sonew Event(type, init)is a working constructor,instanceof Node/Element/CharacterDataanswers truthfully through the linked prototype chain (HTMLElementaliasesElement), andon<event>IDL properties live on theNodeprototype.Node wrappers are built from their layer chain via
from_chain!, backed by theRuntimeState::node_wrappersidentity cache.OwnDataRegistryEach instance's native data slot holds an
OwnDataRegistry(Vec<GcRefCell<Option<Box<dyn OwnSlot>>>>), sized at attach time by the leaf layer's compile-timeOwnBlock::DEPTH; each layer addresses its slot withOwnBlock::IDX. The design is ported from blitz-boa-demo.with_own/with_own_mut/set_own_blockoperate purely on the registry: slot borrows +TypeIddowncasts, entirely on the Rust side. Wrapper construction fills the registry slots throughfrom_chain!.Box<dyn OwnSlot>withTraceon the trait, soJsValues inside layers (e.g.EventLayer.target) stay reachable. Slot access goes throughOption::as_derefto reach&dyn OwnSlotdirectly — resolvingas_any_refon&Box<dyn OwnSlot>would hit the blanket impl on the box itself and break every downcast (the blanket impl also applies to the box, sinceBox<dyn OwnSlot>: Any + Trace)Tested
cargo test -p blitz-vibey-script: 21/21dom.rs, 2/2preact.rs(real Preact render + todo interaction), doctests passcargo check --workspacecleancargo build -p browser --features javascriptbuildsWPT results
5 newly passing, 1 newly failing (net +4), 33 other status changes.
Full diff (39 changed tests)
Generated by the WPT workflow.