1use std::any::{Any, TypeId};
4use std::marker::PhantomData;
5use std::ops::Deref;
6use std::rc::Rc;
7use std::{fmt, iter};
8
9use futures::{Stream, StreamExt};
10
11use super::BaseComponent;
12#[cfg(any(feature = "csr", feature = "ssr"))]
13use super::lifecycle::ComponentState;
14use crate::callback::Callback;
15use crate::context::{ContextHandle, ContextProvider};
16use crate::platform::spawn_local;
17#[cfg(any(feature = "csr", feature = "ssr"))]
18use crate::scheduler::Shared;
19
20#[derive(Clone)]
22pub struct AnyScope {
23 type_id: TypeId,
24 parent: Option<Rc<AnyScope>>,
25 typed_scope: Rc<dyn Any>,
26}
27
28impl fmt::Debug for AnyScope {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 f.debug_struct("AnyScope").finish_non_exhaustive()
31 }
32}
33
34impl<COMP: BaseComponent> From<Scope<COMP>> for AnyScope {
35 fn from(scope: Scope<COMP>) -> Self {
36 AnyScope {
37 type_id: TypeId::of::<COMP>(),
38 parent: scope.parent.clone(),
39 typed_scope: Rc::new(scope),
40 }
41 }
42}
43
44impl AnyScope {
45 pub fn get_parent(&self) -> Option<&AnyScope> {
47 self.parent.as_deref()
48 }
49
50 pub fn get_type_id(&self) -> &TypeId {
52 &self.type_id
53 }
54
55 pub fn downcast<COMP: BaseComponent>(&self) -> Scope<COMP> {
61 self.try_downcast::<COMP>().unwrap()
62 }
63
64 pub fn try_downcast<COMP: BaseComponent>(&self) -> Option<Scope<COMP>> {
68 self.typed_scope.downcast_ref::<Scope<COMP>>().cloned()
69 }
70
71 pub fn find_parent_scope<COMP: BaseComponent>(&self) -> Option<Scope<COMP>> {
75 iter::successors(Some(self), |scope| scope.get_parent())
76 .find_map(AnyScope::try_downcast::<COMP>)
77 }
78
79 pub fn context<T: Clone + PartialEq + 'static>(
82 &self,
83 callback: Callback<T>,
84 ) -> Option<(T, ContextHandle<T>)> {
85 let scope = self.find_parent_scope::<ContextProvider<T>>()?;
86 let scope_clone = scope.clone();
87 let component = scope.get_component()?;
88 Some(component.subscribe_consumer(callback, scope_clone))
89 }
90}
91
92pub struct Scope<COMP: BaseComponent> {
94 _marker: PhantomData<COMP>,
95 parent: Option<Rc<AnyScope>>,
96
97 #[cfg(any(feature = "csr", feature = "ssr"))]
98 pub(crate) pending_messages: MsgQueue<COMP::Message>,
99
100 #[cfg(any(feature = "csr", feature = "ssr"))]
101 pub(crate) state: Shared<Option<ComponentState>>,
102
103 pub(crate) id: usize,
104}
105
106impl<COMP: BaseComponent> fmt::Debug for Scope<COMP> {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 f.debug_struct("Scope<_>").finish_non_exhaustive()
109 }
110}
111
112impl<COMP: BaseComponent> Clone for Scope<COMP> {
113 fn clone(&self) -> Self {
114 Scope {
115 _marker: PhantomData,
116
117 #[cfg(any(feature = "csr", feature = "ssr"))]
118 pending_messages: self.pending_messages.clone(),
119 parent: self.parent.clone(),
120
121 #[cfg(any(feature = "csr", feature = "ssr"))]
122 state: self.state.clone(),
123
124 id: self.id,
125 }
126 }
127}
128
129impl<COMP: BaseComponent> Scope<COMP> {
130 pub fn get_parent(&self) -> Option<&AnyScope> {
132 self.parent.as_deref()
133 }
134
135 pub fn callback<F, IN, M>(&self, function: F) -> Callback<IN>
141 where
142 M: Into<COMP::Message>,
143 F: Fn(IN) -> M + 'static,
144 {
145 let scope = self.clone();
146 let closure = move |input| {
147 let output = function(input);
148 scope.send_message(output);
149 };
150 Callback::from(closure)
151 }
152
153 pub fn batch_callback<F, IN, OUT>(&self, function: F) -> Callback<IN>
165 where
166 F: Fn(IN) -> OUT + 'static,
167 OUT: SendAsMessage<COMP>,
168 {
169 let scope = self.clone();
170 let closure = move |input| {
171 let messages = function(input);
172 messages.send(&scope);
173 };
174 closure.into()
175 }
176
177 pub fn context<T: Clone + PartialEq + 'static>(
180 &self,
181 callback: Callback<T>,
182 ) -> Option<(T, ContextHandle<T>)> {
183 AnyScope::from(self.clone()).context(callback)
184 }
185
186 pub fn send_future<Fut, Msg>(&self, future: Fut)
192 where
193 Msg: Into<COMP::Message>,
194 Fut: Future<Output = Msg> + 'static,
195 {
196 let link = self.clone();
197 spawn_local(async move {
198 let message: COMP::Message = future.await.into();
199 link.send_message(message);
200 });
201 }
202
203 pub fn callback_future<F, Fut, IN, Msg>(&self, function: F) -> Callback<IN>
209 where
210 Msg: Into<COMP::Message>,
211 Fut: Future<Output = Msg> + 'static,
212 F: Fn(IN) -> Fut + 'static,
213 {
214 let link = self.clone();
215
216 let closure = move |input: IN| {
217 link.send_future(function(input));
218 };
219
220 closure.into()
221 }
222
223 pub fn send_future_batch<Fut>(&self, future: Fut)
229 where
230 Fut: Future<Output: SendAsMessage<COMP>> + 'static,
231 {
232 let link = self.clone();
233 let js_future = async move {
234 future.await.send(&link);
235 };
236 spawn_local(js_future);
237 }
238
239 pub fn send_stream<S, M>(&self, stream: S)
253 where
254 M: Into<COMP::Message>,
255 S: Stream<Item = M> + 'static,
256 {
257 let link = self.clone();
258 let js_future = async move {
259 futures::pin_mut!(stream);
260 while let Some(msg) = stream.next().await {
261 let message: COMP::Message = msg.into();
262 link.send_message(message);
263 }
264 };
265 spawn_local(js_future);
266 }
267
268 pub fn get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
270 self.arch_get_component()
271 }
272
273 pub fn send_message<T>(&self, msg: T)
275 where
276 T: Into<COMP::Message>,
277 {
278 self.arch_send_message(msg)
279 }
280
281 pub fn send_message_batch(&self, messages: Vec<COMP::Message>) {
286 self.arch_send_message_batch(messages)
287 }
288}
289
290#[cfg(feature = "ssr")]
291mod feat_ssr {
292 use std::fmt::Write;
293
294 use super::*;
295 use crate::feat_ssr::VTagKind;
296 use crate::html::component::lifecycle::{
297 ComponentRenderState, CreateRunner, DestroyRunner, RenderRunner,
298 };
299 use crate::platform::fmt::BufWriter;
300 use crate::platform::pinned::oneshot;
301 use crate::scheduler;
302 use crate::virtual_dom::Collectable;
303
304 impl<COMP: BaseComponent> Scope<COMP> {
305 pub(crate) async fn render_into_stream(
306 &self,
307 w: &mut BufWriter,
308 props: Rc<COMP::Properties>,
309 hydratable: bool,
310 parent_vtag_kind: VTagKind,
311 ) {
312 let (tx, rx) = oneshot::channel();
317 let state = ComponentRenderState::Ssr { sender: Some(tx) };
318
319 scheduler::push_component_create(
320 self.id,
321 Box::new(CreateRunner {
322 initial_render_state: state,
323 props,
324 scope: self.clone(),
325 #[cfg(feature = "hydration")]
326 prepared_state: None,
327 }),
328 Box::new(RenderRunner {
329 state: self.state.clone(),
330 }),
331 );
332 scheduler::start();
333
334 let collectable = Collectable::for_component::<COMP>();
335
336 if hydratable {
337 collectable.write_open_tag(w);
338 }
339
340 let html = rx.await.unwrap();
341
342 let self_any_scope = AnyScope::from(self.clone());
343 html.render_into_stream(w, &self_any_scope, hydratable, parent_vtag_kind)
344 .await;
345
346 if let Some(prepared_state) = self.get_component().unwrap().prepare_state() {
347 let _ = w.write_str(r#"<script type="application/x-yew-comp-state">"#);
348 let _ = w.write_str(&prepared_state);
349 let _ = w.write_str(r#"</script>"#);
350 }
351
352 if hydratable {
353 collectable.write_close_tag(w);
354 }
355
356 scheduler::push_component_destroy(Box::new(DestroyRunner {
357 state: self.state.clone(),
358 parent_to_detach: false,
359 }));
360 scheduler::start();
361 }
362 }
363}
364
365#[cfg(not(any(feature = "ssr", feature = "csr")))]
366mod feat_no_csr_ssr {
367 use super::*;
368
369 impl<COMP: BaseComponent> Scope<COMP> {
371 pub(super) fn arch_get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
372 Option::<&COMP>::None
373 }
374
375 pub(super) fn arch_send_message<T>(&self, _msg: T)
376 where
377 T: Into<COMP::Message>,
378 {
379 }
380
381 pub(super) fn arch_send_message_batch(&self, _messages: Vec<COMP::Message>) {}
382 }
383}
384
385#[cfg(any(feature = "ssr", feature = "csr"))]
386mod feat_csr_ssr {
387 use std::cell::{Ref, RefCell};
388 use std::sync::atomic::{AtomicUsize, Ordering};
389
390 use super::*;
391 use crate::html::component::lifecycle::UpdateRunner;
392 use crate::scheduler::{self, Shared};
393
394 #[derive(Debug)]
395 pub(crate) struct MsgQueue<Msg>(Shared<Vec<Msg>>);
396
397 impl<Msg> MsgQueue<Msg> {
398 pub fn new() -> Self {
399 MsgQueue(Rc::default())
400 }
401
402 pub fn push(&self, msg: Msg) -> usize {
403 let mut inner = self.0.borrow_mut();
404 inner.push(msg);
405
406 inner.len()
407 }
408
409 pub fn append(&self, other: &mut Vec<Msg>) -> usize {
410 let mut inner = self.0.borrow_mut();
411 inner.append(other);
412
413 inner.len()
414 }
415
416 pub fn drain(&self) -> Vec<Msg> {
417 let mut other_queue = Vec::new();
418 let mut inner = self.0.borrow_mut();
419
420 std::mem::swap(&mut *inner, &mut other_queue);
421
422 other_queue
423 }
424 }
425
426 impl<Msg> Clone for MsgQueue<Msg> {
427 fn clone(&self) -> Self {
428 MsgQueue(self.0.clone())
429 }
430 }
431
432 static COMP_ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
433
434 impl<COMP: BaseComponent> Scope<COMP> {
435 pub(crate) fn new(parent: Option<AnyScope>) -> Self {
437 let parent = parent.map(Rc::new);
438
439 let state = Rc::new(RefCell::new(None));
440
441 let pending_messages = MsgQueue::new();
442
443 Scope {
444 _marker: PhantomData,
445
446 pending_messages,
447
448 state,
449 parent,
450
451 id: COMP_ID_COUNTER.fetch_add(1, Ordering::SeqCst),
452 }
453 }
454
455 #[inline]
456 pub(super) fn arch_get_component(&self) -> Option<impl Deref<Target = COMP> + '_> {
457 self.state.try_borrow().ok().and_then(|state_ref| {
458 Ref::filter_map(state_ref, |state| {
459 state.as_ref().and_then(|m| m.downcast_comp_ref::<COMP>())
460 })
461 .ok()
462 })
463 }
464
465 #[inline]
466 fn schedule_update(&self) {
467 scheduler::push_component_update(Box::new(UpdateRunner {
468 state: self.state.clone(),
469 }));
470 scheduler::start();
472 }
473
474 #[inline]
475 pub(super) fn arch_send_message<T>(&self, msg: T)
476 where
477 T: Into<COMP::Message>,
478 {
479 if self.pending_messages.push(msg.into()) == 1 {
481 self.schedule_update();
482 }
483 }
484
485 #[inline]
486 pub(super) fn arch_send_message_batch(&self, mut messages: Vec<COMP::Message>) {
487 let msg_len = messages.len();
488
489 if self.pending_messages.append(&mut messages) == msg_len {
491 self.schedule_update();
492 }
493 }
494 }
495}
496
497#[cfg(any(feature = "ssr", feature = "csr"))]
498pub(crate) use feat_csr_ssr::*;
499
500#[cfg(feature = "csr")]
501mod feat_csr {
502 use std::cell::Ref;
503
504 use web_sys::Element;
505
506 use super::*;
507 use crate::dom_bundle::{BSubtree, Bundle, DomSlot, DynamicDomSlot};
508 use crate::html::component::lifecycle::{
509 ComponentRenderState, CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner,
510 };
511 use crate::scheduler;
512
513 impl AnyScope {
514 #[cfg(any(test, feature = "test"))]
515 pub(crate) fn test() -> Self {
516 Self {
517 type_id: TypeId::of::<()>(),
518 parent: None,
519 typed_scope: Rc::new(()),
520 }
521 }
522 }
523
524 fn schedule_props_update(
525 state: Shared<Option<ComponentState>>,
526 props: Rc<dyn Any>,
527 next_sibling_slot: DomSlot,
528 ) {
529 scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
530 state,
531 next_sibling_slot: Some(next_sibling_slot),
532 props: Some(props),
533 }));
534 scheduler::start();
536 }
537
538 impl<COMP> Scope<COMP>
539 where
540 COMP: BaseComponent,
541 {
542 pub(crate) fn mount_in_place(
544 &self,
545 root: BSubtree,
546 parent: Element,
547 slot: DomSlot,
548 props: Rc<COMP::Properties>,
549 ) -> DynamicDomSlot {
550 let bundle = Bundle::new();
551 let sibling_slot = DynamicDomSlot::new(slot);
552 let own_slot = DynamicDomSlot::new(sibling_slot.to_position());
553 let shared_slot = own_slot.clone();
554
555 let state = ComponentRenderState::Render {
556 bundle,
557 root,
558 own_slot,
559 parent,
560 sibling_slot,
561 };
562
563 scheduler::push_component_create(
564 self.id,
565 Box::new(CreateRunner {
566 initial_render_state: state,
567 props,
568 scope: self.clone(),
569 #[cfg(feature = "hydration")]
570 prepared_state: None,
571 }),
572 Box::new(RenderRunner {
573 state: self.state.clone(),
574 }),
575 );
576 scheduler::start();
578 shared_slot
579 }
580
581 pub(crate) fn reuse(&self, props: Rc<COMP::Properties>, slot: DomSlot) {
582 schedule_props_update(self.state.clone(), props, slot)
583 }
584 }
585
586 pub(crate) trait Scoped {
587 fn to_any(&self) -> AnyScope;
588 fn render_state(&self) -> Option<Ref<'_, ComponentRenderState>>;
590 fn shift_node(&self, parent: Element, slot: DomSlot);
592 fn destroy(self, parent_to_detach: bool);
594 fn destroy_boxed(self: Box<Self>, parent_to_detach: bool);
595 }
596
597 impl<COMP: BaseComponent> Scoped for Scope<COMP> {
598 fn to_any(&self) -> AnyScope {
599 self.clone().into()
600 }
601
602 fn render_state(&self) -> Option<Ref<'_, ComponentRenderState>> {
603 let state_ref = self.state.borrow();
604
605 state_ref.as_ref()?;
607
608 Some(Ref::map(state_ref, |state_ref| {
609 &state_ref.as_ref().unwrap().render_state
610 }))
611 }
612
613 fn destroy(self, parent_to_detach: bool) {
615 scheduler::push_component_destroy(Box::new(DestroyRunner {
616 state: self.state,
617 parent_to_detach,
618 }));
619 scheduler::start();
621 }
622
623 fn destroy_boxed(self: Box<Self>, parent_to_detach: bool) {
624 self.destroy(parent_to_detach)
625 }
626
627 fn shift_node(&self, parent: Element, slot: DomSlot) {
628 let mut state_ref = self.state.borrow_mut();
629 if let Some(render_state) = state_ref.as_mut() {
630 render_state.render_state.shift(parent, slot)
631 }
632 }
633 }
634}
635#[cfg(feature = "csr")]
636pub(crate) use feat_csr::*;
637
638#[cfg(feature = "hydration")]
639mod feat_hydration {
640 use wasm_bindgen::JsCast;
641 use web_sys::{Element, HtmlScriptElement};
642
643 use super::*;
644 use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot, Fragment};
645 use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner};
646 use crate::scheduler;
647 use crate::virtual_dom::Collectable;
648
649 impl<COMP> Scope<COMP>
650 where
651 COMP: BaseComponent,
652 {
653 pub(crate) fn hydrate_in_place(
662 &self,
663 root: BSubtree,
664 parent: Element,
665 fragment: &mut Fragment,
666 props: Rc<COMP::Properties>,
667 prev_next_sibling: &mut Option<DynamicDomSlot>,
668 ) -> DynamicDomSlot {
669 tracing::trace!(
673 component.id = self.id,
674 "hydration(type = {})",
675 std::any::type_name::<COMP>()
676 );
677
678 let collectable = Collectable::for_component::<COMP>();
679
680 let mut fragment = Fragment::collect_between(fragment, &collectable, &parent);
681
682 let prepared_state = match fragment
683 .back()
684 .cloned()
685 .and_then(|m| m.dyn_into::<HtmlScriptElement>().ok())
686 {
687 Some(m) if m.type_() == "application/x-yew-comp-state" => {
688 fragment.pop_back();
689 parent.remove_child(&m).unwrap();
690 Some(m.text().unwrap())
691 }
692 _ => None,
693 };
694
695 let own_slot = match fragment.front().cloned() {
696 Some(first_node) => DynamicDomSlot::new(DomSlot::at(first_node)),
697 None => DynamicDomSlot::new(DomSlot::at_end()),
698 };
699 let shared_slot = own_slot.clone();
700 let sibling_slot = DynamicDomSlot::new_debug_trapped();
701 if let Some(prev_next_sibling) = prev_next_sibling {
702 prev_next_sibling.reassign(shared_slot.to_position());
703 }
704 *prev_next_sibling = Some(sibling_slot.clone());
705 let state = ComponentRenderState::Hydration {
706 parent,
707 root,
708 own_slot,
709 sibling_slot,
710 fragment,
711 };
712
713 scheduler::push_component_create(
714 self.id,
715 Box::new(CreateRunner {
716 initial_render_state: state,
717 props,
718 scope: self.clone(),
719 prepared_state,
720 }),
721 Box::new(RenderRunner {
722 state: self.state.clone(),
723 }),
724 );
725
726 scheduler::start();
728 shared_slot
729 }
730 }
731}
732
733pub trait SendAsMessage<COMP: BaseComponent> {
737 fn send(self, scope: &Scope<COMP>);
740}
741
742impl<COMP> SendAsMessage<COMP> for Option<COMP::Message>
743where
744 COMP: BaseComponent,
745{
746 fn send(self, scope: &Scope<COMP>) {
747 if let Some(msg) = self {
748 scope.send_message(msg);
749 }
750 }
751}
752
753impl<COMP> SendAsMessage<COMP> for Vec<COMP::Message>
754where
755 COMP: BaseComponent,
756{
757 fn send(self, scope: &Scope<COMP>) {
758 scope.send_message_batch(self);
759 }
760}