Skip to content

niso coupling inventory

Generated from docs/architecture/inventory.md when the site was built at 7aa23e2.

Snapshot: origin/main at 226cd3a (code identical to 3e94aa3; only DECISIONS.md moved). Paths without a crate prefix are in crates/niso-core/src/; capi: is crates/niso-capi/, glue.js is bindings/js/glue.js, rt.h is crates/niso-capi/include/niso_runtime.h. Every reference was read at that commit. Counts come from two scripts kept in ~/.capy/work/arch/tools/ (fields.py, calls2.py); they are grep-based, so the counts are approximate, but every row below was spot-checked and the false positives I found were removed.

1. The shape of the problem in one paragraph

Section titled “1. The shape of the problem in one paragraph”

Dom (lib.rs:321-446) has 90 fields. 27 of them belong to a web API subsystem or to the event loop rather than to the document, and every subsystem also reaches four shared match statements and three shared loops: the ObjectData enum (lib.rs:135-196, 52 variants), interface_of (lib.rs:737-827), free_object (lib.rs:876-951), the core Task enum (lib.rs:301-312), the run loop (event_loop.rs:273-387) with its readiness predicate run_ready (event_loop.rs:175-185), the per-node cleanup in collect (lib.rs:1056-1085) and the rendering steps (lib.rs:1209-1298). The C side mirrors it: one hand-written header for every subsystem (rt.h, 1027 lines), one generator pass over all of idl/ (capi:build.rs:9-15), and one 4132-line glue.js. A new platform API therefore edits at least lib.rs, event_loop.rs, rt.h, capi:src/lib.rs and glue.js. That is what the churn shows: since 2026-10-01, of 214 non-merge commits, 50 touched glue.js, 42 lib.rs, 41 api.rs, 38 rt.h, 26 capi:src/lib.rs and 25 event_loop.rs; the in-flight branches add crypto (plat-blob-abort-crypto) and worker-host (engine-napi) rules to the same places (section 9).

2. Every field of Dom, who touches it, and where it should live

Section titled “2. Every field of Dom, who touches it, and where it should live”

“Modules” is how many files read or write the field through self., d., dom. or (*dom).. “Proposed owner” is the crate that owns the state after the split in architecture.md; “core” means it stays in the document core (DOM, style, layout, render, events, input stay together, per the scoping note).

Field Type Declared Modules Uses (file:first line(count)) Proposed owner
doc BaseDocument lib.rs:322 21 api.rs:93(91), lib.rs:584(39), tree.rs:48(24), input.rs:81(20), loading.rs:213(15), animation_style.rs:52(15), events.rs:193(14), api/cssom.rs:68(14), api/documents.rs:58(7), api/custom_elements.rs:124(7), api/attributes.rs:34(6), template.rs:169(5), geometry.rs:20(4), render.rs:439(3), event_loop.rs:131(3), serialize.rs:51(2), api/traversal.rs:123(2), api/history.rs:35(2), api/namespaces.rs:34(2), api/resize.rs:77(2), api/base.rs:15(1) core
objects SlotMap<ObjKey, Object> lib.rs:323 2 lib.rs:579(10), worker.rs:167(1) niso-host (table type); core keeps the instance
node_refs HashMap<NodeId, u32> lib.rs:324 1 lib.rs:842(7) core
listeners HashMap<Obj, Vec<Rc<Listener>>> lib.rs:325 2 events.rs:212(5), lib.rs:878(2) core (events)
pending_moves Vec<(Obj, Obj)> lib.rs:326 1 lib.rs:993(4) core
candidates Vec<NodeId> lib.rs:327 1 lib.rs:858(6) core
tree_hook Option<(TreeHookFn, *mut c_void)> lib.rs:328 1 lib.rs:666(3) core
inline_styles HashMap<NodeId, Obj> lib.rs:329 2 api.rs:2577(2), lib.rs:881(1) core
token_lists HashMap<NodeId, Obj> lib.rs:330 2 api.rs:1864(2), lib.rs:885(1) core
string_maps HashMap<NodeId, Obj> lib.rs:331 2 api.rs:2921(2), lib.rs:889(1) core
contexts HashMap<NodeId, Obj> lib.rs:333 2 api.rs:3821(2), lib.rs:922(1) niso-canvas
canvases HashMap<NodeId, canvas::Bitmap> lib.rs:334 4 canvas.rs:91(6), lib.rs:1060(4), render.rs:143(2), api.rs:3825(1) niso-canvas
web_sockets websocket::WebSockets lib.rs:335 1 websocket.rs:273(13) niso-websocket
pointers input::Pointers lib.rs:336 1 input.rs:154(18) core (input)
media_queries Vec<Obj> lib.rs:338 3 lib.rs:925(2), event_loop.rs:184(1), api.rs:2494(1) core (style)
template_of HashMap<NodeId, NodeId> lib.rs:340 2 lib.rs:1034(3), tree.rs:425(1) core
input_values HashMap<NodeId, String> lib.rs:341 4 events.rs:527(5), api.rs:3230(5), lib.rs:1061(4), tree.rs:394(2) core (forms)
input_checked HashMap<NodeId, bool> lib.rs:342 4 events.rs:385(4), lib.rs:1062(4), api.rs:3497(3), tree.rs:397(2) core (forms)
selection api::forms::Selection lib.rs:343 1 api/forms.rs:91(7) core (forms)
control_sizes [[f32; 2]; 5] lib.rs:345 1 lib.rs:1336(2) core (controls)
control_font_size f32 lib.rs:346 1 lib.rs:1353(3) core (controls)
button_style controls::ButtonStyle lib.rs:347 1 lib.rs:1367(3) core (controls)
field_style controls::FieldStyle lib.rs:348 2 lib.rs:1308(6), events.rs:527(3) core (controls)
controls_sheet UserAgentStylesheet lib.rs:349 1 lib.rs:1390(2) core (controls)
changed_controls HashSet<NodeId> lib.rs:351 2 events.rs:625(5), lib.rs:1063(1) core (controls)
window Obj lib.rs:352 8 api.rs:1180(4), events.rs:189(3), lib.rs:578(3), worker.rs:166(3), input.rs:209(3), api/history.rs:57(2), messaging.rs:705(1), api/messaging.rs:26(1) core
started Instant lib.rs:353 2 lib.rs:676(1), messaging.rs:657(1) niso-sched (clock)
clock_offset f64 lib.rs:355 2 lib.rs:676(2), messaging.rs:657(1) niso-sched (clock)
scratch_str String lib.rs:356 1 ffi.rs:295(2) core (ffi)
scratch_handles Vec<u64> lib.rs:357 1 ffi.rs:307(2) core (ffi)
scratch_strings Vec<String> lib.rs:358 1 ffi.rs:312(1) core (ffi)
scratch_strs Vec<NisoStr> lib.rs:359 1 ffi.rs:313(2) core (ffi)
scratch_bytes Rc<[u8]> lib.rs:360 1 ffi.rs:323(2) core (ffi)
scratch_text Rc<str> lib.rs:361 1 ffi.rs:350(2) core (ffi)
last_error String lib.rs:362 1 ffi.rs:187(2) core (ffi)
render render::RenderState lib.rs:363 1 lib.rs:1167(5) core (render)
surfaces template::SurfaceState lib.rs:364 1 lib.rs:1318(4) niso-surfaces
net std::sync::Arc<net::Net> lib.rs:365 5 event_loop.rs:178(5), lib.rs:594(4), websocket.rs:452(4), loading.rs:196(3), messaging.rs:664(1) niso-fetch (transport); core keeps a ResourceLoader handle
net_scratch Vec<u8> lib.rs:366 1 lib.rs:643(3) niso-fetch
broken_images HashSet<String> lib.rs:368 2 lib.rs:1269(1), api.rs:3168(1) core (loading)
detached_images HashMap<NodeId, String> lib.rs:371 2 lib.rs:1058(2), tree.rs:240(1) core (loading)
media media::MediaState lib.rs:372 4 api.rs:158(12), lib.rs:606(9), render.rs:179(5), event_loop.rs:183(1) niso-media
idb indexeddb::Idb lib.rs:373 3 indexeddb/mod.rs:466(43), api/indexeddb.rs:383(4), api.rs:2347(1) niso-indexeddb
media_scratch Vec<u8> lib.rs:374 1 lib.rs:630(5) niso-media
pending_scrolls Vec<NodeId> lib.rs:377 2 lib.rs:1155(3), event_loop.rs:182(1) core (rendering steps)
media_queries_stale bool lib.rs:379 2 lib.rs:658(2), event_loop.rs:184(1) core (style)
canvas_src std::cell::Cell<Option<bool>> lib.rs:382 3 loading.rs:229(2), event_loop.rs:136(2), api/mutation.rs:89(1) core (animating check)
event_loop event_loop::EventLoop lib.rs:383 1 event_loop.rs:62(24) niso-sched
modules modules::Modules lib.rs:384 2 modules.rs:228(15), websocket.rs:266(2) niso-modules
scripts loading::Scripts lib.rs:385 2 loading.rs:200(18), api.rs:3400(2) core (loading)
session api::history::Session lib.rs:386 4 api/history.rs:31(14), media.rs:204(4), loading.rs:214(1), storage/mod.rs:382(1) core (history)
location Option<Obj> lib.rs:387 3 api.rs:2410(2), api/messaging.rs:283(1), api/location.rs:10(1) core (history)
history Option<Obj> lib.rs:388 2 api.rs:2415(1), api/history.rs:67(1) core (history)
node_iterators Vec<Obj> lib.rs:390 1 api/traversal.rs:54(3) core
documents HashMap<NodeId, api::documents::DocumentData> lib.rs:392 2 api/documents.rs:62(3), lib.rs:816(2) core
node_documents HashMap<NodeId, NodeId> lib.rs:394 2 api/documents.rs:58(4), lib.rs:1069(1) core
node_kinds HashMap<NodeId, api::documents::NodeKind> lib.rs:397 5 api/attributes.rs:17(4), lib.rs:803(3), api/documents.rs:101(3), tree.rs:391(2), api.rs:406(1) core
implementations HashMap<NodeId, Obj> lib.rs:399 2 api/documents.rs:142(2), lib.rs:901(1) core
attr_nodes HashMap<NodeId, Vec<NodeId>> lib.rs:401 2 api/attributes.rs:40(5), api/documents.rs:79(1) core
attribute_maps HashMap<NodeId, Obj> lib.rs:403 2 api/attributes.rs:185(2), lib.rs:905(1) core
scripting std::sync::Arc<std::sync::atomic::AtomicBool> lib.rs:405 1 loading.rs:108(4) core (loading)
resize_observers Vec<Obj> lib.rs:407 1 api/resize.rs:97(5) core (style)
style_sheets HashMap<NodeId, Obj> lib.rs:408 2 api/cssom.rs:78(2), lib.rs:894(1) core (cssom)
style_sheet_list Option<Obj> lib.rs:409 1 api.rs:1186(2) core (cssom)
cssom_objects Vec<Obj> lib.rs:411 1 api/cssom.rs:55(5) core (cssom)
registered HashMap<NodeId, Vec<api::mutation::Registered>> lib.rs:413 1 api/mutation.rs:72(11) core (mutation)
mutation_observers Vec<Obj> lib.rs:415 1 api/mutation.rs:267(3) core (mutation)
pending_mutation_observers Vec<Obj> lib.rs:416 1 api/mutation.rs:158(6) core (mutation)
mutation_microtask_queued bool lib.rs:417 1 api/mutation.rs:206(3) core (mutation)
mutation_microtask_signal bool lib.rs:420 3 api/mutation.rs:208(2), animation.rs:863(1), api/custom_elements.rs:199(1) core (mutation; also written by animation.rs)
microtask_hook Option<(api::mutation::MicrotaskHookFn, *mut c_void)> lib.rs:421 1 api/mutation.rs:213(2) core (mutation) -> niso-sched checkpoint hook
anim animation::Animations lib.rs:422 4 animation.rs:392(29), animation_style.rs:427(8), event_loop.rs:315(1), api/animations.rs:169(1) niso-animations
local_storage Option<Obj> lib.rs:423 1 api.rs:2425(1) niso-webstorage
screen Option<Obj> lib.rs:424 1 api.rs:2438(1) core
custom_element_registry Option<Obj> lib.rs:425 1 api.rs:2443(1) core
custom_elements api::custom_elements::CustomElements lib.rs:426 1 api/custom_elements.rs:121(31) core
crypto Option<Obj> lib.rs:427 1 api.rs:2342(1) niso-crypto
performance Option<Obj> lib.rs:428 2 api/timing.rs:119(9), api.rs:2337(1) niso-performance
performance_entries Vec<Obj> lib.rs:430 1 api/timing.rs:82(6) niso-performance
time_origin f64 lib.rs:432 1 api/timing.rs:125(1) niso-performance
session_storage Option<Obj> lib.rs:433 1 api.rs:2433(1) niso-webstorage
storage storage::Storage lib.rs:434 4 api/storage.rs:59(9), worker.rs:160(2), storage/mod.rs:336(2), indexeddb/mod.rs:461(1) niso-storage (drivers) + niso-webstorage (areas)
cookies Vec<(String, String)> lib.rs:436 1 api.rs:1264(3) core (document.cookie)
tasks std::collections::VecDeque<Task> lib.rs:437 3 lib.rs:1396(2), websocket.rs:169(1), event_loop.rs:176(1) niso-sched (core keeps its DOM-manipulation queue as a Source)
load_event Option<VoidFunction> lib.rs:439 3 websocket.rs:452(2), lib.rs:1283(1), api.rs:2404(1) core (loading)
messaging messaging::Messaging lib.rs:440 6 messaging.rs:444(17), worker.rs:168(14), websocket.rs:452(4), event_loop.rs:181(2), api/messaging.rs:151(2), modules.rs:259(1) niso-messaging
worker Option<Box<worker::Scope>> lib.rs:442 3 worker.rs:175(13), api/messaging.rs:283(2), messaging.rs:525(1) niso-messaging (workers)
worker_runner Option<WorkerRunnerFn> lib.rs:443 1 worker.rs:170(3) niso-messaging (workers)
message_hook Option<(MessageHookFn, *mut c_void)> lib.rs:444 2 lib.rs:662(1), messaging.rs:644(1) niso-messaging
wasm WasmSlot lib.rs:445 1 lib.rs:1408(3) niso-wasm

Summary of the table:

  • 62 fields stay in the core. Most are touched by 1 or 2 files, which is healthy: doc (21 files) is the document itself.
  • 27 fields move to a web API crate or to the scheduler, and objects keeps its place but becomes niso-host’s table type. The ones touched outside their own subsystem are the coupling: net (event_loop.rs, websocket.rs, loading.rs, messaging.rs), messaging (worker.rs, websocket.rs, event_loop.rs, api/messaging.rs, modules.rs), media (api.rs, render.rs, event_loop.rs), anim (animation_style.rs, event_loop.rs, api/animations.rs), storage (worker.rs, indexeddb/mod.rs), canvases (render.rs, api.rs), tasks (event_loop.rs, websocket.rs), load_event (websocket.rs:452, lib.rs:1283, api.rs:2404), modules (websocket.rs:266).
  • Three fields are the clock and the loop (started, clock_offset, event_loop) and become niso-sched.
  • mutation_microtask_signal is written by animation.rs:863 and api/custom_elements.rs:199 as well as its owner, which is how animations and custom elements piggyback on the mutation-observer microtask. Under the design this becomes a checkpoint hook.

3. Central match statements and lists that every subsystem edits

Section titled “3. Central match statements and lists that every subsystem edits”
Place What each subsystem adds Evidence
ObjectData one or more variants per object type lib.rs:135-196 (IdbRequest…IdbCursor 177-182, MessagePort..WorkerLocation 184-191, Animation/KeyframeEffect/Timeline 192-195, WebSocket 173, Canvas2D 172)
interface_of one arm per variant lib.rs:737-797
free_object cleanup arm that reaches into the subsystem’s state lib.rs:876-951 (free_idb 927, free_animation 929, media_queries.retain 925, message ports 945)
Task (core queue) a variant per task source the core queues for lib.rs:301-312 (Idb, Wasm), run in lib.rs:1426-1449
run_ready an || clause event_loop.rs:175-185 (modules 177, net 178-179, WebSockets 180, messaging 181, media 183)
Dom::run a branch event_loop.rs:283-312 (WebSocket and modules 285, messages 289, fetch task end 299-308)
wait_network knowledge of every source that can deliver from another thread websocket.rs:446-513 reads messaging (452, 466, 485), net (452, 466, 474), workers (447, 484) and the load event (452)
collect per-node side-table cleanup lib.rs:1057-1079: images, listeners, canvases, input values, scripts, mutation registrations, custom elements, media, documents, attrs
rendering steps a step lib.rs:1209-1298: scrolls, media events (1217), media queries, image loads via net (1248), load event (1282), animations (1290), animation frames (1291), resize observers (1293)
C call epilogue a delivery after every C API call ffi.rs:194-208 and 221-235: tree moves, scripts, element queue, mutation microtask, animation promises (deliver_animation_promises, animation.rs:522)
checkpoint end cleanup event_loop.rs:216-220 calls idb_cleanup (indexeddb/mod.rs:812)
EventExtra a payload variant per event interface events.rs:52-73 (Message, Close, Storage, VersionChange, AnimationPlayback, MediaQuery)
event path non-node parents events.rs:294-312 hard-codes idb_parent (indexeddb/mod.rs:732) for non-node targets
ErrorCode codes lib.rs:82-119; plat-blob-abort-crypto adds Operation = 207
Window getters a singleton field plus getter per API api.rs:2334-2349 (performance, crypto, indexedDB), 2419-2445 (localStorage, sessionStorage, screen, customElements)
html_interface element interfaces lib.rs:199-277 (stays in core; elements are the document’s)

Edges are “file A calls a function defined only in file B” with count and one example. The full list (183 edges, std-named functions filtered out) is tools/calls2.txt; this is the subsystem-relevant part.

4.1 Into the run loop (who the loop calls)

Section titled “4.1 Into the run loop (who the loop calls)”
From To Through Ref
event_loop modules module_tasks_pending, module_budget, run_module_task event_loop.rs:177, 275, 285
event_loop net has_done, has_loads, done_ids, drop_done event_loop.rs:178-179, 299-307
event_loop websocket web_sockets_pending, web_sockets_unwatched, run_task, wait_network event_loop.rs:180, 197, 285, 338
event_loop messaging has_items, message_limit, run_message, answer_loads, waits_past event_loop.rs:181, 281-282, 289, 297
event_loop worker worker_closing event_loop.rs:278
event_loop animation animations_need_frames, anim.last_update event_loop.rs:131, 315
event_loop indexeddb idb_cleanup event_loop.rs:219
event_loop lib (render steps) rendering_steps, update_rendering, run_tasks event_loop.rs:283, 309, 382
lib (rendering_steps) media, net, animation, resize media.events, net.take_loads, update_animations, deliver_resize_observations lib.rs:1217, 1248, 1290, 1293
lib (run_tasks) indexeddb, wasm, history indexeddb::run_task, wasm().run_task, history::traverse lib.rs:1437-1444
From To Count Through Ref
websocket messaging 6 children_active, has_items, agent.wait, Message, message_event websocket.rs:452, 466, 485, 550-551
websocket net 5 resources_pending, has_done, busy, has_loads websocket.rs:452, 466, 474
websocket modules 2 the shared Waker websocket.rs:26, 266
websocket worker / event_loop 3 is_worker, nothing_scheduled websocket.rs:447, 484
messaging worker 3+ worker_failed, worker::Shared messaging.rs:27-ish imports, 715
worker messaging 10 adopt, agents, ports worker.rs:168-178
worker storage 3 configure for the worker’s DOM worker.rs:160
worker loading 3 load_resource (the window’s loader) worker.rs:133
messaging net 1 load_local (answers a worker’s load) messaging.rs:664
net loading, modules 2 Loader, Waker net.rs:14-15
api/messaging worker 19 start_worker and the Worker members api/messaging.rs:253
indexeddb storage 3 driver_for, storage_origin indexeddb/mod.rs:461, 501
indexeddb, key_path, storage/key structured_clone 32 js_string, own_property, buffer_source indexeddb/key_path.rs:52, storage/key.rs:187
api/storage storage 3 driver_for api/storage.rs:67
animation_style animation 8 effect, keyframes animation_style.rs:204
animation animation_style 1 target_properties animation.rs:1160
animation api/mutation 1 deliver_microtask animation.rs:1243
api/mutation animation 1 notify_finished_animations api/mutation.rs:288
render media, controls, template 7 shown_state, control, is_surface render.rs:401, 504, 489
template render, controls 4 radii, control template.rs:209, 190

Two things stand out. Nothing in storage/, structured_clone.rs, indexeddb/key_path.rs, net.rs (except two imports), wasm.rs or media.rs reaches into Dom (section 7), so they are close to standalone already. The genuinely tangled cluster is websocket + messaging + worker + net, and the tangle is entirely about the event loop: “can anything still arrive from another thread, and how do I wait for it” (wait_network, websocket.rs:446-513). That knowledge belongs to the scheduler, fed by each source.

4.3 Into events (who fires events, and how)

Section titled “4.3 Into events (who fires events, and how)”

Every subsystem fires through Dom::fire_at / Dom::fire / Dom::dispatch (events.rs:343, 495, 503) with a raw *mut Dom, which is how script reenters: websocket.rs:538-564, messaging.rs:631-651 (dispatch_made), worker.rs:298, indexeddb/mod.rs:1214-1217, animation.rs:1259, input.rs:229, lib.rs:1214-1279 (rendering steps). Each needs a payload variant in EventExtra (events.rs:52-73) and IndexedDB needs the event path special case (events.rs:304-309).

5. Where the C API, api.rs, the IDL and glue.js tie subsystems together

Section titled “5. Where the C API, api.rs, the IDL and glue.js tie subsystems together”
  • One pass over all of idl/ (17 files, 2671 lines) generates: core’s InterfaceId enum and callback types (crates/niso-core/build.rs:2-6, included at lib.rs:69-72), capi’s C shims and niso.h (capi:build.rs:9-15), QuickJS’s host table and the generated part of the glue (crates/niso-quickjs/build.rs:17-20, spliced at glue.js:1098 /*@GENERATED@*/), Porffor’s (crates/niso-porffor/build.rs:56-57) and the JVM classes.
  • Every generated shim calls niso_core::api::<snake interface>::<member> (crates/niso-idl/src/emit.rs:504, 524, 544, 559, 580, 593). So the implementation of every interface, whichever subsystem owns it, has to be reachable at api::<iface> in niso-core: api.rs:3699 mod indexeddb, 3705 web_socket, 3750 mod messaging, 3786 html_canvas_element, 3835 canvas_rendering_context2_d, 2962 html_media_element, 2507 crypto, 2300-2350 window_or_worker_global_scope (timers, performance, crypto, indexedDB).
  • The IDL is already split by subsystem file, and three subsystems already add themselves to the global with partials: indexeddb.webidl:75, webcrypto.webidl:6, performance.webidl:9. Web Storage (partial interface Window, html.webidl:21-26, Storage 33, StorageEvent 55) and HTMLMediaElement (html.webidl:361) are still in html.webidl.
  • Extended attributes in use: 64 [NisoGlue] and 64 [NisoInternal] members (implemented or called only by the glue), 17 [NisoReentrant], 10 [NisoHeld], 2 [NisoNative], 2 [NisoValue]. [NisoGlue] members tie an IDL file to a specific section of glue.js.

5.2 The hand-written C API (capi:src/lib.rs, 682 lines; rt.h, 1027 lines)

Section titled “5.2 The hand-written C API (capi:src/lib.rs, 682 lines; rt.h, 1027 lines)”
Subsystem capi:src rt.h
DOM lifecycle, hooks, collect lib.rs:18-205 274-323
Storage lib.rs:155-170 112-272 (driver vtable), 312-313
Event loop lib.rs:71, 101-115 324-379
Workers and messages lib.rs:76-100 381-401
Render stream, surfaces, templates lib.rs:208-240, 529-565 402-664, 783-789
Input and controls lib.rs:242-345, 462-527 665-782
Net (fetch transport) lib.rs:351-395 791-810
Media and media session lib.rs:396-460 813-836
Native modules lib.rs:586-690 837-885
WebAssembly capi:src/wasm.rs (263) 886-959
Structured clone decode capi:src/clone.rs (305) 961-1018
Handles lib.rs:567-584 1020-1027

Bindings install nine hooks to make the subsystems work: task (rt.h:284), tree (283), script (285), microtask (289), message (394), worker runner (393), module (885), wasm call and task (920) and wake (379). The wake hook is one Waker shared by modules, the network and the window’s messaging agent (lib.rs:470-471, modules.rs:257-260, messaging.rs:94-95).

Lines Section Owner after the split
1-235 wrapper identity, cache, FinalizationRegistry, indexed/named proxies core glue
239-880 SC, structured serialization niso-structured-clone glue
881-1100 WebIDL conversions, callbacks table (cbs, 971), [NisoGlue] plumbing, /*@GENERATED@*/ core glue
1100-1185 DOMException, QuotaExceededError, makeError core glue (niso-base)
1186-1712 IndexedDB [NisoGlue] members niso-indexeddb glue
1713-1880 global, postMessage, MessageEvent data, workers niso-messaging glue
1881-2095 CSSOM camel-case, custom elements core glue
2096-2163 console, reportError core glue
2164-2195 timers niso-sched glue
2196-2338 fetch, Headers, Response over niso_net_* niso-fetch glue
2339-2466 media play promises, media session niso-media glue
2467-2498 globals list (fetch, navigator, …) composed
2499-2869 Web Animations niso-animations glue
2870-2994 script processing core glue
2995-3001 tasks(): the glue’s own task sources, fetch and media actions removed: become Rust sources
3002-3231 native modules niso-modules glue
3232-4110 WebAssembly JS API niso-wasm glue
4110-4132 the hooks object returned to the engine composed

The glue runs two task sources of its own (settleFetches and runMediaActions, glue.js:2998-3000) from the engine’s task hook (QE_HOOK_TASKS, niso_js.c:931-937). That is why the Rust run loop has a special branch that gives finished fetches a task end and then drops what nobody took (event_loop.rs:299-308): the scheduler cannot see a source that lives in JS.

State Kind Ref Notes
PORTS LazyLock<Mutex<HashMap<u64, Port>>> messaging.rs:217 process-wide port registry: entangled ports, owner agent, queue
NEXT_ID AtomicU64 messaging.rs:218 port ids
CHANNELS LazyLock<Mutex<Vec<Channel>>> messaging.rs:381 BroadcastChannel registry
POSTS AtomicU64 messaging.rs:100 race detection in children_active
REMOVING thread_local! messaging.rs:43-48 worklist for port removal
SURFACES_SHEET, DEFAULT_CONTROLS LazyLock<UserAgentStylesheet> lib.rs:461-465 immutable, fine
current _Thread_local niso_js* bindings/js/niso_js.c:41 the engine per thread
Waker Arc, shared by modules, net, agent lib.rs:470-471 one wake hook for every cross-thread producer
worker threads one full Dom each worker.rs:157-158 Dom::new(1, 1, 1.0) builds a blitz document and stylist for a worker that never renders
storage directory env and platform dirs storage/mod.rs (default_directory), docs/storage.md resolved per DOM

No other statics exist in the workspace crates (grep for static, thread_local!, LazyLock, OnceLock across crates/*/src). The messaging registries are legitimately process-wide (ports move between threads), so the design keeps them, inside niso-messaging, with their lifetime documented in its contract.

7. How close each subsystem already is to standalone

Section titled “7. How close each subsystem already is to standalone”

Measured as references to Dom internals in the file:

File Lines impl Dom *mut Dom fns ObjectData:: Imports from the crate
structured_clone.rs 804 0 0 0 ErrorCode, Result, err only (structured_clone.rs:18)
storage/{ffi,key,memory,sqlite}.rs 1339 0 0 0 ErrorCode::QuotaExceeded, structured_clone::js_string
storage/mod.rs 494 1 0 0 Dom, api::storage::Area (the impl Dom is the setup API)
indexeddb/key_path.rs 267 0 0 0 storage::key::Key, structured_clone
wasm.rs 950 0 0 0 Error, Dom::queue_wasm_task
net.rs 315 0 0 0 loading::Loader, modules::Waker
media.rs 235 0 0 0 none
modules.rs 339 1 2 0 crate root
websocket.rs 589 1 1 5 events, ffi, messaging::Message, Waker
messaging.rs + api/messaging.rs 1251 1 6 11 events, ffi, worker::Shared, Waker
worker.rs 455 1 4 5 messaging, storage, loading, events
indexeddb/mod.rs + ops.rs + api/indexeddb.rs 2663 5 5 19 storage, structured_clone, events
animation.rs + api/animations.rs 1743 1 2 10 animation_style, events, mutation
animation_style.rs 541 1 0 0 15 uses of self.doc (stylo)
template.rs 398 0 0 0 5 uses of doc
canvas.rs 172 1 0 0 crate root

flush_layout (lib.rs:1114-1127) runs on every frame, surface render, template render, scroll (lib.rs:1146), hit test (input.rs:94) and geometry read. It calls three subsystems in a fixed order: doc.handle_messages, disable_links (loading.rs:403), apply_animated_styles (animation_style.rs:468), then doc.resolve under catch_unwind with a retry. render_frame then remakes every canvas bitmap (lib.rs:1305-1307) and walks the tree with a fresh nodes: HashMap::new() each frame (render.rs:208-217).

Dom::dispatch (events.rs:343-392) and dispatch_to_path (394-452): per dispatch it allocates the path (event_path, events.rs:294) and clones it into the event (e.path = path.clone(), 409); invoke (454-492) runs once per path entry per phase and each time clones the event type String, looks the target up in the listeners HashMap and collects matching listeners into a new Vec<Rc<Listener>>. A click on an element 20 deep is about 40 hash lookups and 40 vector allocations before any listener runs. Every event is a slotmap object (create_event, events.rs:181) freed afterwards.

  • needs_frame (event_loop.rs:163-165), which a presenting host calls every display frame, evaluates run_ready: about four mutex locks plus one per native module (net.rs:143-150, messaging.rs:139-141, modules.rs:309-311), and animating (130-132), which scans the whole tree (css_animating, 149-151) whenever blitz reports animating and the document has a <canvas src>.
  • run (273-387) per iteration: run_tasks swaps out the whole VecDeque (lib.rs:1427, so its buffer is dropped and regrown), module_budget allocates a Vec per run (modules.rs:303-306), timers are found by a linear min_by (316-318), and collect runs after every task (287, 296, 310, 385).
  • The JS binding’s task hook (niso_js.c:931-937) runs a checkpoint, the glue’s tasks() hook, and qj_sweep (niso_js.c:149-153), which scans the whole id table, so a page holding 100k wrappers pays 100k iterations at the end of every task.

8.4 C API calls (every DOM call from every language)

Section titled “8.4 C API calls (every DOM call from every language)”

ffi::call and call_reentrant (ffi.rs:194-235) wrap each call in push_element_queue, catch_unwind, and five deliveries: tree moves, scripts, the element queue, the mutation microtask and animation promises. Each is cheap when empty, but the list is a coupling point: animations hook every C call.

The render stream (render.rs, RenderState 87-98, frame 208) and the templates (template.rs, SurfaceState) consume doc, form state (controls::Forms), media state and canvases, borrowed together in lib.rs:1308-1309 and 1317-1318. That is the only place the renderer reads subsystem state, so a RenderContributor hook (media, canvas) is enough to decouple it.

9. In-flight branches against these hotspots

Section titled “9. In-flight branches against these hotspots”

Diffs are git diff origin/main...origin/<branch>.

Branch Base Commits Hotspots touched
storage-fixes (merging next) 52608766 13 storage/* (mod.rs +290, sqlite.rs +203, ffi.rs, key.rs, memory.rs), structured_clone.rs (+197), event_loop.rs (+39, restructures run), lib.rs (+32), rt.h, capi lib.rs, glue.js, worker.rs, api/storage.rs
sched-followups f500e555 5 event_loop.rs (+19), modules.rs (+34), websocket.rs (+164)
plat-blob-abort-crypto 26e16680 (63 commits behind main) 42 glue.js (+2144), api.rs (+74), new api/abort.rs, api/crypto.rs, event_loop.rs (+27: crypto_busy in run_ready, a crypto branch in run), events.rs, lib.rs (+35: AbortController/AbortSignal variants, crypto_jobs, secure_context, abort_algorithms fields), messaging.rs, net.rs (+144), structured_clone.rs, websocket.rs, worker.rs, idl (dom, url, webcrypto, workers, xhr), new crate niso-crypto
engine-napi 52608766 31 glue.js (+155), niso_js.c, rt.h (+31: worker host), capi lib.rs, lib.rs (+35: object_owner), messaging.rs, worker.rs (+141), new crate niso-napi
tv-input not on origin ? from its commit message (~/.capy/work/tv-input/commitmsg.txt): Gamepad API, controller keys, focus ring, niso_dom_gamepad_*, niso_dom_set_input, niso_dom_focus_ring, niso_tv_viewport, so input.rs, events.rs, lib.rs, rt.h, capi, glue.js, a new IDL file. Not verified.
native-look local only ? not inspected; by name controls.rs, render.rs and hosts. Not verified.

The storage-fixes run loop was written against a base 16 event_loop.rs commits behind main (git log 52608766..origin/main -- event_loop.rs), and plat-blob-abort-crypto adds a third set of rules (crypto_busy) to the same predicate. Every one of these is a subsystem teaching the shared loop about itself.