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