1pub mod application;
5mod bindings;
6mod branch;
7mod children;
8pub mod coherent;
9mod commit;
10mod component;
11mod content;
12#[doc(hidden)]
13pub mod controls;
14#[doc(hidden)]
15pub use children::Children;
16#[cfg(feature = "islands")]
17pub mod delivery;
18mod hydration;
19mod keyed;
20mod mount;
21mod property;
22mod range;
23mod reconcile;
24mod strings;
25mod target;
26#[doc(hidden)]
27pub mod text_value;
28
29pub use content::Content;
30#[doc(hidden)]
31pub use mount::{NestingGuard, TemplateNodes};
32#[doc(hidden)]
33pub use range::{Anchors, MountPoint};
34pub use target::{ElementTarget, InputTarget};
35
36use crate::{Effect, Owner, OwnerHandle, batch};
37use std::{cell::RefCell, collections::VecDeque, rc::Rc, thread::LocalKey};
38pub use wasm_bindgen::JsValue;
39use wasm_bindgen::{JsCast, closure::Closure};
40use web_sys::{Document, Element, Event, EventTarget, HtmlTemplateElement};
41
42pub trait Component: Sized + 'static {
46 const TEMPLATE_HASH: &'static str = "";
47 const TEMPLATE_HTML: &'static str = "";
48 fn mount(self) -> Result<Scope, JsValue>;
49
50 fn mount_with(make: impl FnOnce(OwnerHandle) -> Self) -> Result<Scope, JsValue> {
52 Self::try_mount_with(|owner| Ok(make(owner)))
53 }
54
55 fn try_mount_with(
56 make: impl FnOnce(OwnerHandle) -> Result<Self, JsValue>,
57 ) -> Result<Scope, JsValue> {
58 let scope = Self::prepare_component(None, Box::new(make))?;
59 scope.try_commit()?;
60 Ok(scope)
61 }
62
63 fn prepare(
67 parent: &OwnerHandle,
68 make: impl FnOnce(OwnerHandle) -> Result<Self, JsValue>,
69 ) -> Result<Scope, JsValue> {
70 Self::prepare_component(Some(parent), Box::new(make))
71 }
72
73 #[doc(hidden)]
76 fn prepare_component(
77 parent: Option<&OwnerHandle>,
78 make: ComponentFactory<'_, Self>,
79 ) -> Result<Scope, JsValue> {
80 let owner = parent.map(Owner::child).unwrap_or_default();
81 let mut scope = make(owner.handle())?.mount()?;
82 if let Some(owned) = scope.hydration_ownership.value(&scope.owner) {
85 scope.hydration_ownership = HydrationOwnership::Preserved(owned);
86 }
87 scope.owner = owner;
88 scope.prepare_queue(parent);
89 Ok(scope)
90 }
91}
92
93#[doc(hidden)]
96pub type ComponentFactory<'a, C> = Box<dyn FnOnce(OwnerHandle) -> Result<C, JsValue> + 'a>;
97
98pub trait TemplateComponent: Component {}
101
102pub use crate::FromInputs;
104
105pub trait IntoMountError {
112 fn into_mount_error(self) -> JsValue;
113}
114
115impl IntoMountError for JsValue {
116 fn into_mount_error(self) -> JsValue {
117 self
118 }
119}
120
121impl IntoMountError for std::convert::Infallible {
122 fn into_mount_error(self) -> JsValue {
123 match self {}
124 }
125}
126
127impl IntoMountError for String {
128 fn into_mount_error(self) -> JsValue {
129 JsValue::from_str(&self)
130 }
131}
132
133impl IntoMountError for &str {
134 fn into_mount_error(self) -> JsValue {
135 JsValue::from_str(self)
136 }
137}
138
139pub fn document() -> Result<Document, JsValue> {
140 web_sys::window()
141 .and_then(|window| window.document())
142 .ok_or_else(|| JsValue::from_str("fusor requires a browser document"))
143}
144
145pub fn element(tag: &str) -> Result<Element, JsValue> {
147 document()?.create_element(tag)
148}
149
150fn missing(selector: &str) -> JsValue {
151 JsValue::from_str(&format!("fusor: no element matches {selector:?}"))
152}
153
154const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
155
156fn is_html(element: &Element) -> bool {
157 element.namespace_uri().as_deref() == Some(HTML_NAMESPACE)
158}
159
160fn scoped<T: 'static, R>(
162 key: &'static LocalKey<RefCell<T>>,
163 value: T,
164 run: impl FnOnce() -> R,
165) -> R {
166 struct Restore<T: 'static>(&'static LocalKey<RefCell<T>>, Option<T>);
167 impl<T: 'static> Drop for Restore<T> {
168 fn drop(&mut self) {
169 if let Some(previous) = self.1.take() {
170 self.0.set(previous);
171 }
172 }
173 }
174 let _restore = Restore(key, Some(key.replace(value)));
175 run()
176}
177
178fn cached<T: Clone + 'static>(
181 registry: &'static LocalKey<RefCell<VecDeque<T>>>,
182 limit: usize,
183 matches: impl Fn(&T) -> bool,
184 make: impl FnOnce() -> T,
185) -> T {
186 if let Some(found) =
187 registry.with_borrow(|entries| entries.iter().rev().find(|entry| matches(entry)).cloned())
188 {
189 return found;
190 }
191 let entry = make();
192 registry.with_borrow_mut(|entries| {
193 if entries.len() >= limit {
194 entries.pop_front();
195 }
196 entries.push_back(entry.clone());
197 });
198 entry
199}
200
201fn with_native_root<R>(
203 root: Option<&Element>,
204 make: impl FnOnce() -> Result<R, JsValue>,
205) -> Result<R, JsValue> {
206 match root {
207 #[cfg(feature = "islands")]
208 Some(root) => hydration::with_root(root, make),
209 _ => make(),
210 }
211}
212
213fn remove_tree(root: &Element) {
215 #[cfg(feature = "islands")]
216 delivery::dispose_tree(root);
217 root.remove();
218}
219
220type Handler = Rc<dyn Fn(Event)>;
221
222#[derive(Default)]
226struct Handlers {
227 slots: Vec<(u32, Option<Handler>)>,
228 free: Vec<u32>,
229}
230
231impl Handlers {
232 fn insert(&mut self, handler: Handler) -> (u32, u32) {
233 if let Some(slot) = self.free.pop() {
234 let entry = &mut self.slots[slot as usize];
235 entry.1 = Some(handler);
236 return (slot, entry.0);
237 }
238 self.slots.push((0, Some(handler)));
239 ((self.slots.len() - 1) as u32, 0)
240 }
241
242 fn get(&self, slot: u32, generation: u32) -> Option<Handler> {
243 let (current, handler) = self.slots.get(slot as usize)?;
244 (*current == generation).then(|| handler.clone())?
245 }
246
247 fn remove(&mut self, slot: u32, generation: u32) -> Option<Handler> {
249 let entry = self
250 .slots
251 .get_mut(slot as usize)
252 .filter(|(current, _)| *current == generation)?;
253 entry.0 = entry.0.wrapping_add(1);
254 self.free.push(slot);
255 entry.1.take()
256 }
257}
258
259thread_local! {
260 static HANDLERS: RefCell<Handlers> = RefCell::new(Handlers::default());
261 static DISPATCH: Closure<dyn Fn(u32, u32, Event)> = Closure::new(|slot, generation, event| {
264 let handler = HANDLERS.with_borrow(|handlers| handlers.get(slot, generation));
265 if let Some(handler) = handler {
266 handler(event);
267 }
268 });
269}
270
271enum ListenerTarget {
274 Node(EventTarget),
275 Bundle(Rc<JsValue>, u32),
276}
277
278pub struct Listener {
280 target: ListenerTarget,
281 event: strings::EventName,
282 slot: u32,
283 generation: u32,
284}
285
286impl Listener {
287 pub fn new(
290 target: EventTarget,
291 event: &str,
292 handler: impl FnMut(Event) + 'static,
293 ) -> Result<Self, JsValue> {
294 Self::attach(ListenerTarget::Node(target), event, handler)
295 }
296
297 fn attach(
298 target: ListenerTarget,
299 event: &str,
300 handler: impl FnMut(Event) + 'static,
301 ) -> Result<Self, JsValue> {
302 let handler = RefCell::new(handler);
303 let handler: Handler = Rc::new(move |event| (handler.borrow_mut())(event));
304 let (slot, generation) = HANDLERS.with_borrow_mut(|handlers| handlers.insert(handler));
305 let event = strings::EventName::from(event);
306 let listening = DISPATCH.with(|dispatch| match &target {
307 ListenerTarget::Node(node) => {
308 strings::listen(node, &event, dispatch.as_ref(), slot, generation)
309 }
310 ListenerTarget::Bundle(nodes, index) => {
311 strings::listen_bundle(nodes, *index, &event, dispatch.as_ref(), slot, generation)
312 }
313 });
314 match listening {
315 Ok(()) => Ok(Self {
316 target,
317 event,
318 slot,
319 generation,
320 }),
321 Err(error) => {
322 let handler =
323 HANDLERS.with_borrow_mut(|handlers| handlers.remove(slot, generation));
324 drop(handler);
325 Err(error)
326 }
327 }
328 }
329
330 fn batched(
332 target: ListenerTarget,
333 event: &str,
334 active: impl Fn() -> bool + 'static,
335 mut handler: impl FnMut(Event) + 'static,
336 ) -> Result<Self, JsValue> {
337 Self::attach(target, event, move |event| {
338 if active() {
339 batch(|| handler(event));
340 }
341 })
342 }
343}
344
345impl Drop for Listener {
346 fn drop(&mut self) {
347 let _ = match &self.target {
348 ListenerTarget::Node(node) => strings::remove(node, &self.event, self.slot),
349 ListenerTarget::Bundle(nodes, index) => {
350 strings::unlisten_bundle(nodes, *index, &self.event, self.slot)
351 }
352 };
353 let handler =
354 HANDLERS.with_borrow_mut(|handlers| handlers.remove(self.slot, self.generation));
355 drop(handler);
356 }
357}
358
359#[must_use = "retain the scope to keep its DOM bindings and event listeners active"]
362pub struct Scope {
363 owner: Owner,
364 root: Element,
365 fragment: Option<MountPoint>,
366 effects: Vec<Effect>,
367 listeners: Vec<Listener>,
368 children: Vec<Scope>,
369 component_state: Option<Rc<dyn std::any::Any>>,
370 retained: Vec<Box<dyn std::any::Any>>,
371 mount_queue: Option<Rc<commit::CommitQueue>>,
372 mount_parent: Option<OwnerHandle>,
373 remove_on_drop: bool,
374 render_tree: Option<Rc<coherent::Tree>>,
375 hydrating: bool,
376 hydration_ownership: HydrationOwnership,
377}
378
379#[derive(Clone, Copy)]
383enum HydrationOwnership {
384 Untracked,
385 CurrentOwner,
386 Preserved(bool),
387}
388impl HydrationOwnership {
389 fn value(self, owner: &Owner) -> Option<bool> {
390 match self {
391 Self::Untracked => None,
392 Self::CurrentOwner => Some(owner.was_activated()),
393 Self::Preserved(owned) => Some(owned),
394 }
395 }
396}
397
398impl Drop for Scope {
399 fn drop(&mut self) {
400 self.owner.dispose();
401 self.effects.clear();
402 self.listeners.clear();
403 self.children.clear();
404 self.retained.clear();
405 self.component_state.take();
406 let hydrated_owned = self.hydration_ownership.value(&self.owner);
407 if !self.hydrating || hydrated_owned == Some(true) {
408 if let Some(fragment) = &self.fragment {
409 fragment.remove();
410 }
411 }
412 if self.remove_on_drop && hydrated_owned.unwrap_or(true) {
413 remove_tree(&self.root);
414 }
415 }
416}
417
418impl crate::render::Scope for Scope {
419 fn owner(&self) -> OwnerHandle {
420 self.owner()
421 }
422
423 fn retain_state<T: 'static>(&mut self, state: T) -> Rc<T> {
424 self.retain_state(state)
425 }
426
427 fn prepares_effects(&self) -> bool {
428 self.prepares_effects()
429 }
430}
431
432impl Scope {
433 pub fn new(root: Element) -> Self {
434 let owner = Owner::new();
435 owner.commit();
436 Self::with_owner(root, owner)
437 }
438
439 fn new_prepared(root: Element, parent: Option<&OwnerHandle>) -> Self {
442 Self::with_owner(root, parent.map(Owner::child).unwrap_or_default())
443 }
444
445 fn with_owner(root: Element, owner: Owner) -> Self {
446 Self {
447 owner,
448 root,
449 fragment: None,
450 effects: Vec::new(),
451 listeners: Vec::new(),
452 children: Vec::new(),
453 component_state: None,
454 retained: Vec::new(),
455 mount_queue: None,
456 mount_parent: None,
457 remove_on_drop: false,
458 render_tree: None,
459 hydrating: false,
460 hydration_ownership: HydrationOwnership::Untracked,
461 }
462 }
463
464 pub fn at(selector: &str) -> Result<Self, JsValue> {
466 Ok(Self::new(
467 document()?
468 .query_selector(selector)?
469 .ok_or_else(|| missing(selector))?,
470 ))
471 }
472
473 pub fn from_template(selector: &str) -> Result<Self, JsValue> {
476 let template = document()?
477 .query_selector(selector)?
478 .ok_or_else(|| missing(selector))?
479 .dyn_into::<HtmlTemplateElement>()
480 .map_err(|_| JsValue::from_str("fusor: expected an HTML template"))?;
481 Self::clone_template(&template)
482 }
483
484 fn clone_template(template: &HtmlTemplateElement) -> Result<Self, JsValue> {
485 Ok(Self::new(Self::clone_template_root(template)?))
486 }
487
488 fn clone_template_root(template: &HtmlTemplateElement) -> Result<Element, JsValue> {
489 let content = template.content();
490 if content.child_element_count() != 1 {
491 return Err(JsValue::from_str(
492 "fusor: a row template needs exactly one root element",
493 ));
494 }
495 Ok(content
496 .first_element_child()
497 .expect("one template element")
498 .clone_node_with_deep(true)?
499 .dyn_into::<Element>()?)
500 }
501
502 pub fn root(&self) -> &Element {
503 &self.root
504 }
505
506 pub fn owner(&self) -> OwnerHandle {
507 self.owner.handle()
508 }
509
510 pub fn dispose(&self) {
512 self.owner.dispose();
513 }
514
515 #[doc(hidden)]
516 pub fn is_hydrating(&self) -> bool {
517 self.hydrating
518 }
519
520 #[doc(hidden)]
521 pub fn prepares_effects(&self) -> bool {
522 #[cfg(feature = "islands")]
523 if delivery::enabled() {
524 return true;
525 }
526 self.is_coherent()
527 }
528
529 pub fn commit(&self) {
533 if let Err(error) = self.try_commit() {
534 self.owner.dispose();
535 web_sys::console::error_1(&error);
536 }
537 }
538
539 #[doc(hidden)]
540 pub fn prepare_owner(&mut self, parent: Option<&OwnerHandle>) {
541 self.owner = parent.map(Owner::child).unwrap_or_default();
543 self.finish_owner_preparation(parent);
544 }
545
546 fn finish_owner_preparation(&mut self, parent: Option<&OwnerHandle>) {
547 self.prepare_queue(parent);
548 self.prepare_coherent(parent);
549 #[cfg(feature = "islands")]
550 delivery::prepare_preview_owner(&self.owner(), parent);
551 if self.hydrating {
552 self.hydration_ownership = HydrationOwnership::CurrentOwner;
556 }
557 }
558
559 pub fn retain(&mut self, guard: impl std::any::Any) {
561 self.retained.push(Box::new(guard));
562 }
563
564 pub fn attach(&mut self, container: &Element) -> Result<(), JsValue> {
566 container.append_child(&self.root)?;
567 self.remove_on_drop = true;
568 Ok(())
569 }
570
571 #[doc(hidden)]
573 pub fn retain_state<C: 'static>(&mut self, value: C) -> Rc<C> {
574 let state = Rc::new(value);
575 self.component_state = Some(state.clone());
576 state
577 }
578
579 pub fn adopt(&mut self, child: Scope) {
581 child.commit();
582 self.children.push(child);
583 }
584
585 pub fn mount_child(
588 &mut self,
589 target: impl ElementTarget,
590 mut child: Scope,
591 ) -> Result<(), JsValue> {
592 target.resolve(self)?.append_child(&child.root)?;
593 child.remove_on_drop = true;
594 child.try_commit()?;
595 self.children.push(child);
596 Ok(())
597 }
598
599 pub fn select(&self, selector: &str) -> Result<Element, JsValue> {
601 if selector == ":scope" || self.root.matches(selector)? {
602 return Ok(self.root.clone());
603 }
604 self.root
605 .query_selector(selector)?
606 .ok_or_else(|| missing(selector))
607 }
608}
609
610#[cfg(test)]
611mod handler_tests {
612 use super::*;
613
614 fn handler() -> Handler {
615 Rc::new(|_| {})
616 }
617
618 #[test]
619 fn released_slots_reject_stale_generations_and_are_reused() {
620 let mut handlers = Handlers::default();
621 let first = handler();
622 let (slot, generation) = handlers.insert(first.clone());
623 assert!(Rc::ptr_eq(&handlers.get(slot, generation).unwrap(), &first));
624 assert!(handlers.get(slot, generation + 1).is_none());
625 assert!(Rc::ptr_eq(
626 &handlers.remove(slot, generation).unwrap(),
627 &first
628 ));
629 assert!(handlers.get(slot, generation).is_none());
630 assert!(handlers.remove(slot, generation).is_none());
631 let second = handler();
632 let (reused, next) = handlers.insert(second.clone());
633 assert_eq!(reused, slot);
634 assert_ne!(next, generation);
635 assert!(handlers.get(slot, generation).is_none(), "stale listener");
636 assert!(handlers.remove(slot, generation).is_none(), "stale removal");
637 assert!(Rc::ptr_eq(&handlers.get(slot, next).unwrap(), &second));
638 let (other, _) = handlers.insert(handler());
639 assert_ne!(other, slot);
640 assert!(handlers.get(99, 0).is_none());
641 }
642}