1use crate::{
2 renderer::{CastFrom, Rndr},
3 view::{Position, PositionState},
4};
5#[cfg(any(debug_assertions, leptos_debuginfo))]
6use std::cell::Cell;
7use std::{cell::RefCell, panic::Location, rc::Rc};
8use web_sys::{Comment, Element, Node, Text};
9
10#[cfg(feature = "mark_branches")]
11const COMMENT_NODE: u16 = 8;
12
13#[derive(Debug)]
19pub struct Cursor(Rc<RefCell<crate::renderer::types::Node>>);
20
21impl Clone for Cursor {
22 fn clone(&self) -> Self {
23 Self(Rc::clone(&self.0))
24 }
25}
26
27impl Cursor
28where
29 crate::renderer::types::Element: AsRef<crate::renderer::types::Node>,
30{
31 pub fn new(root: crate::renderer::types::Element) -> Self {
33 let root = <crate::renderer::types::Element as AsRef<
34 crate::renderer::types::Node,
35 >>::as_ref(&root)
36 .clone();
37 Self(Rc::new(RefCell::new(root)))
38 }
39
40 pub fn current(&self) -> crate::renderer::types::Node {
42 self.0.borrow().clone()
43 }
44
45 pub fn child(&self) {
49 let mut inner = self.0.borrow_mut();
50 if let Some(node) = Rndr::first_child(&inner) {
51 *inner = node;
52 }
53
54 #[cfg(feature = "mark_branches")]
55 {
56 while inner.node_type() == COMMENT_NODE {
57 if let Some(content) = inner.text_content() {
58 if content.starts_with("bo") || content.starts_with("bc") {
59 if let Some(sibling) = Rndr::next_sibling(&inner) {
60 *inner = sibling;
61 continue;
62 }
63 }
64 }
65
66 break;
67 }
68 }
69 }
73
74 pub fn sibling(&self) {
78 let mut inner = self.0.borrow_mut();
79 if let Some(node) = Rndr::next_sibling(&inner) {
80 *inner = node;
81 }
82
83 #[cfg(feature = "mark_branches")]
84 {
85 while inner.node_type() == COMMENT_NODE {
86 if let Some(content) = inner.text_content() {
87 if content.starts_with("bo") || content.starts_with("bc") {
88 if let Some(sibling) = Rndr::next_sibling(&inner) {
89 *inner = sibling;
90 continue;
91 }
92 }
93 }
94 break;
95 }
96 }
97 }
101
102 pub fn parent(&self) {
106 let mut inner = self.0.borrow_mut();
107 if let Some(node) = Rndr::get_parent(&inner) {
108 *inner = node;
109 }
110 }
111
112 pub fn set(&self, node: crate::renderer::types::Node) {
114 *self.0.borrow_mut() = node;
115 }
116
117 pub fn next_placeholder(
119 &self,
120 position: &PositionState,
121 ) -> crate::renderer::types::Placeholder {
122 self.advance_to_placeholder(position);
125 let marker = self.current();
126 crate::renderer::types::Placeholder::cast_from(marker.clone())
127 .unwrap_or_else(|| failed_to_cast_marker_node(marker))
128 }
129
130 pub fn advance_to_placeholder(&self, position: &PositionState) {
132 if position.get() == Position::FirstChild {
133 self.child();
134 } else {
135 self.sibling();
136 }
137 position.set(Position::NextChild);
138 }
139}
140
141#[cfg(any(debug_assertions, leptos_debuginfo))]
142thread_local! {
143 static CURRENTLY_HYDRATING: Cell<Option<&'static Location<'static>>> = const { Cell::new(None) };
144}
145
146pub(crate) fn set_currently_hydrating(
147 location: Option<&'static Location<'static>>,
148) {
149 #[cfg(any(debug_assertions, leptos_debuginfo))]
150 {
151 CURRENTLY_HYDRATING.set(location);
152 }
153 #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
154 {
155 _ = location;
156 }
157}
158
159pub(crate) fn failed_to_cast_element(tag_name: &str, node: Node) -> Element {
160 #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
161 {
162 _ = node;
163 unreachable!();
164 }
165 #[cfg(any(debug_assertions, leptos_debuginfo))]
166 {
167 let hydrating = CURRENTLY_HYDRATING
168 .take()
169 .map(|n| n.to_string())
170 .unwrap_or_else(|| "{unknown}".to_string());
171 web_sys::console::error_3(
172 &wasm_bindgen::JsValue::from_str(&format!(
173 "A hydration error occurred while trying to hydrate an \
174 element defined at {hydrating}.\n\nThe framework expected an \
175 HTML <{tag_name}> element, but found this instead: ",
176 )),
177 &node,
178 &wasm_bindgen::JsValue::from_str(
179 "\n\nThe hydration mismatch may have occurred slightly \
180 earlier, but this is the first time the framework found a \
181 node of an unexpected type.",
182 ),
183 );
184 panic!(
185 "Unrecoverable hydration error. Please read the error message \
186 directly above this for more details."
187 );
188 }
189}
190
191pub(crate) fn failed_to_cast_marker_node(node: Node) -> Comment {
192 #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
193 {
194 _ = node;
195 unreachable!();
196 }
197 #[cfg(any(debug_assertions, leptos_debuginfo))]
198 {
199 let hydrating = CURRENTLY_HYDRATING
200 .take()
201 .map(|n| n.to_string())
202 .unwrap_or_else(|| "{unknown}".to_string());
203 web_sys::console::error_3(
204 &wasm_bindgen::JsValue::from_str(&format!(
205 "A hydration error occurred while trying to hydrate an \
206 element defined at {hydrating}.\n\nThe framework expected a \
207 marker node, but found this instead: ",
208 )),
209 &node,
210 &wasm_bindgen::JsValue::from_str(
211 "\n\nThe hydration mismatch may have occurred slightly \
212 earlier, but this is the first time the framework found a \
213 node of an unexpected type.",
214 ),
215 );
216 panic!(
217 "Unrecoverable hydration error. Please read the error message \
218 directly above this for more details."
219 );
220 }
221}
222
223pub(crate) fn failed_to_cast_text_node(node: Node) -> Text {
224 #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
225 {
226 _ = node;
227 unreachable!();
228 }
229 #[cfg(any(debug_assertions, leptos_debuginfo))]
230 {
231 let hydrating = CURRENTLY_HYDRATING
232 .take()
233 .map(|n| n.to_string())
234 .unwrap_or_else(|| "{unknown}".to_string());
235 web_sys::console::error_3(
236 &wasm_bindgen::JsValue::from_str(&format!(
237 "A hydration error occurred while trying to hydrate an \
238 element defined at {hydrating}.\n\nThe framework expected a \
239 text node, but found this instead: ",
240 )),
241 &node,
242 &wasm_bindgen::JsValue::from_str(
243 "\n\nThe hydration mismatch may have occurred slightly \
244 earlier, but this is the first time the framework found a \
245 node of an unexpected type.",
246 ),
247 );
248 panic!(
249 "Unrecoverable hydration error. Please read the error message \
250 directly above this for more details."
251 );
252 }
253}