blitz_dom_api/lib.rs
1//! Runtime-agnostic DOM operations over [`blitz_dom`].
2//!
3//! `blitz-script` is currently the only path from a language runtime to the
4//! DOM, and its operations are entangled with Boa: `JsValue` in, `JsResult`
5//! out, a `Context` threaded through, and prototype objects holding the
6//! registration. A second runtime cannot reuse any of it. This crate is those
7//! operations with the runtime removed, so that a binding does argument
8//! coercion and result construction and nothing else.
9//!
10//! Nothing here depends on Boa, and nothing here may. See `tests/no_boa.rs`.
11//!
12//! # Shape of the API
13//!
14//! Every operation is a free function taking the document first, then the node
15//! the operation is *on* (what a binding would call `this`), then the
16//! arguments in the order the DOM method declares them:
17//!
18//! ```ignore
19//! let child = document::create_element(&mut doc, "div")?;
20//! node::append_child(&mut doc, parent, child)?;
21//! element::set_attribute(&mut doc, child, "class", "panel")?;
22//! ```
23//!
24//! Readers take `&BaseDocument`, mutators take `&mut BaseDocument`. Every
25//! operation returns `Result<T, DomError>`, including the ones that cannot
26//! fail today, so that a caller written against this API keeps compiling when
27//! one of them grows a failure case.
28//!
29//! # Borrow discipline
30//!
31//! **No return value keeps a document borrow alive.** Readers that would
32//! naturally hand back `&str` (`text_content`, `inner_html`, `get_attribute`,
33//! `css_text`, `get_property_value`) return owned `String` instead. That is a
34//! deliberate allocation.
35//!
36//! The reason is re-entrancy. A binding calls out to guest code between
37//! operations, and guest code calls back in; if a borrow were still live
38//! across that boundary the next mutation would panic inside `RefCell`, at a
39//! call site with no relationship to the code that took the borrow. Paying an
40//! allocation on every read makes that class of failure unrepresentable. A
41//! borrow taken *inside* an operation may span engine-internal work freely; it
42//! just may not outlive the call.
43//!
44//! # What this crate deliberately does not do
45//!
46//! - **It does not mark layout dirty and it does not request a redraw.**
47//! `blitz-script` routes mutations through `DomCtx::mutate_doc`, which sets
48//! a dirty flag and asks the shell for a frame. Both are properties of the
49//! embedding, not of the operation, so both stay with the binding. A binding
50//! that forgets gets stale geometry reads, which is the exact bug that flag
51//! exists to prevent.
52//! - **It does not flush layout.** [`geometry::bounding_client_rect`] reads
53//! whatever layout currently holds. See that function's documentation.
54//! - **It does not upgrade custom elements.** `blitz-script` runs
55//! `upgrade_if_defined` after an insertion, which constructs a guest object.
56//! That is a runtime operation.
57//! - **It has no events.** Event objects, dispatch, listener registration,
58//! selection, pointer capture and focus are all out of scope: they need a
59//! dispatch model that belongs with the runtime binding. See README.md.
60//!
61//! # Interning
62//!
63//! [`atom::Interner`] and [`atom::AtomId`] exist for a guest that cannot
64//! cheaply pass strings across its boundary. The binding owns an interner,
65//! resolves an incoming `AtomId` to a `&str`, and calls the operation. The
66//! resolution happens one level up rather than inside every operation, which
67//! is what keeps the signatures here from having to change when a guest with
68//! that constraint arrives. See [`atom`] for the ownership rule.
69
70pub mod atom;
71pub mod character_data;
72pub mod document;
73pub mod element;
74pub mod error;
75pub mod geometry;
76pub mod node;
77pub mod style;
78
79#[cfg(test)]
80mod test_support;
81
82pub use atom::{AtomId, Interner};
83pub use error::DomError;
84pub use geometry::Rect;
85
86/// Re-exported so a caller does not have to depend on `blitz-dom` directly
87/// just to name the id type an operation takes.
88pub use blitz_dom::NodeId;
89
90/// Shorthand for the result type every operation returns.
91pub type Result<T> = std::result::Result<T, DomError>;