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