1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use cfg_if::cfg_if;
use std::{cell::RefCell, fmt::Display};

cfg_if! {
  if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
    use once_cell::unsync::Lazy as LazyCell;
    use std::collections::HashMap;
    use wasm_bindgen::JsCast;

    // We can tell if we start in hydration mode by checking to see if the
    // id "_0-0-0" is present in the DOM. If it is, we know we are hydrating from
    // the server, if not, we are starting off in CSR
    thread_local! {
      static HYDRATION_COMMENTS: LazyCell<HashMap<String, web_sys::Comment>> = LazyCell::new(|| {
        let document = crate::document();
        let body = document.body().unwrap();
        let walker = document
          .create_tree_walker_with_what_to_show(&body, 128)
          .unwrap();
        let mut map = HashMap::new();
        while let Ok(Some(node)) = walker.next_node() {
          if let Some(content) = node.text_content() {
            if let Some(hk) = content.strip_prefix("hk=") {
              if let Some(hk) = hk.split('|').next() {
                map.insert(hk.into(), node.unchecked_into());
              }
            }
          }
        }
        map
      });

      #[cfg(debug_assertions)]
      pub(crate) static VIEW_MARKERS: LazyCell<HashMap<String, web_sys::Comment>> = LazyCell::new(|| {
        let document = crate::document();
        let body = document.body().unwrap();
        let walker = document
          .create_tree_walker_with_what_to_show(&body, 128)
          .unwrap();
        let mut map = HashMap::new();
        while let Ok(Some(node)) = walker.next_node() {
          if let Some(content) = node.text_content() {
            if let Some(id) = content.strip_prefix("leptos-view|") {
              map.insert(id.into(), node.unchecked_into());
            }
          }
        }
        map
      });

      static IS_HYDRATING: RefCell<LazyCell<bool>> = RefCell::new(LazyCell::new(|| {
        #[cfg(debug_assertions)]
        return crate::document().get_element_by_id("_0-0-0").is_some()
          || crate::document().get_element_by_id("_0-0-0o").is_some()
          || HYDRATION_COMMENTS.with(|comments| comments.get("_0-0-0o").is_some());

        #[cfg(not(debug_assertions))]
        return crate::document().get_element_by_id("_0-0-0").is_some()
          || HYDRATION_COMMENTS.with(|comments| comments.get("_0-0-0").is_some());
      }));
    }

    pub(crate) fn get_marker(id: &str) -> Option<web_sys::Comment> {
      HYDRATION_COMMENTS.with(|comments| comments.get(id).cloned())
    }
  }
}

/// A stable identifier within the server-rendering or hydration process.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct HydrationKey {
    /// The key of the previous component.
    pub previous: String,
    /// The element offset within the current component.
    pub offset: usize,
}

impl Display for HydrationKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}{}", self.previous, self.offset)
    }
}

impl Default for HydrationKey {
    fn default() -> Self {
        Self {
            previous: "0-".to_string(),
            offset: 0,
        }
    }
}

thread_local!(static ID: RefCell<HydrationKey> = Default::default());

/// Control and utility methods for hydration.
pub struct HydrationCtx;

impl HydrationCtx {
    /// Get the next `id` without incrementing it.
    pub fn peek() -> HydrationKey {
        ID.with(|id| id.borrow().clone())
    }

    /// Increments the current hydration `id` and returns it
    pub fn id() -> HydrationKey {
        ID.with(|id| {
            let mut id = id.borrow_mut();
            id.offset = id.offset.wrapping_add(1);
            id.clone()
        })
    }

    /// Resets the hydration `id` for the next component, and returns it
    pub fn next_component() -> HydrationKey {
        ID.with(|id| {
            let mut id = id.borrow_mut();
            let offset = id.offset;
            id.previous.push_str(&offset.to_string());
            id.previous.push('-');
            id.offset = 0;
            id.clone()
        })
    }

    #[doc(hidden)]
    #[cfg(not(all(target_arch = "wasm32", feature = "web")))]
    pub fn reset_id() {
        ID.with(|id| *id.borrow_mut() = Default::default());
    }

    /// Resumes hydration from the provided `id`. Useful for
    /// `Suspense` and other fancy things.
    pub fn continue_from(id: HydrationKey) {
        ID.with(|i| *i.borrow_mut() = id);
    }

    #[cfg(all(target_arch = "wasm32", feature = "web"))]
    pub(crate) fn stop_hydrating() {
        IS_HYDRATING.with(|is_hydrating| {
            std::mem::take(&mut *is_hydrating.borrow_mut());
        })
    }

    #[cfg(all(target_arch = "wasm32", feature = "web"))]
    pub(crate) fn is_hydrating() -> bool {
        IS_HYDRATING.with(|is_hydrating| **is_hydrating.borrow())
    }

    pub(crate) fn to_string(id: &HydrationKey, closing: bool) -> String {
        #[cfg(debug_assertions)]
        return format!("_{id}{}", if closing { 'c' } else { 'o' });

        #[cfg(not(debug_assertions))]
        {
            let _ = closing;

            format!("_{id}")
        }
    }
}