1use std::any::Any;
2use std::borrow::Cow;
3use std::cell::RefCell;
4use std::cmp::Ordering;
5use std::collections::VecDeque;
6use std::fmt;
7use std::rc::Rc;
8
9use crate::history::History;
10use crate::listener::HistoryListener;
11use crate::location::Location;
12use crate::utils::{
13 assert_absolute_path, assert_no_fragment, assert_no_query, get_id, WeakCallback,
14};
15#[cfg(feature = "query")]
16use crate::{error::HistoryResult, query::ToQuery};
17
18#[derive(Debug)]
20struct LocationStack {
21 prev: Vec<Location>,
22 next: VecDeque<Location>,
23 current: Location,
24}
25
26impl LocationStack {
27 fn current(&self) -> Location {
28 self.current.clone()
29 }
30
31 fn len(&self) -> usize {
32 self.prev.len() + self.next.len() + 1
33 }
34
35 fn go(&mut self, delta: isize) {
36 match delta.cmp(&0) {
37 Ordering::Greater => {
39 for _i in 0..delta {
40 if let Some(mut m) = self.next.pop_front() {
41 std::mem::swap(&mut m, &mut self.current);
42
43 self.prev.push(m);
44 }
45 }
46 }
47 Ordering::Less => {
49 for _i in 0..-delta {
50 if let Some(mut m) = self.prev.pop() {
51 std::mem::swap(&mut m, &mut self.current);
52
53 self.next.push_front(m);
54 }
55 }
56 }
57 Ordering::Equal => {}
59 }
60 }
61
62 fn push(&mut self, mut location: Location) {
63 std::mem::swap(&mut location, &mut self.current);
64
65 self.prev.push(location);
66 self.next.clear();
68 }
69
70 fn replace(&mut self, location: Location) {
71 self.current = location;
72 }
73}
74
75impl Default for LocationStack {
76 fn default() -> Self {
77 Self {
78 prev: Vec::new(),
79 next: VecDeque::new(),
80 current: Location {
81 path: "/".to_string().into(),
82 query_str: "".to_string().into(),
83 hash: "".to_string().into(),
84 state: None,
85 id: Some(get_id()),
86 },
87 }
88 }
89}
90
91#[derive(Clone, Default)]
97pub struct MemoryHistory {
98 inner: Rc<RefCell<LocationStack>>,
99 callbacks: Rc<RefCell<Vec<WeakCallback>>>,
100}
101
102impl PartialEq for MemoryHistory {
103 fn eq(&self, rhs: &Self) -> bool {
104 Rc::ptr_eq(&self.inner, &rhs.inner)
105 }
106}
107
108impl fmt::Debug for MemoryHistory {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 f.debug_struct("MemoryHistory").finish()
111 }
112}
113
114impl History for MemoryHistory {
115 fn len(&self) -> usize {
116 self.inner.borrow().len()
117 }
118
119 fn go(&self, delta: isize) {
120 self.inner.borrow_mut().go(delta)
121 }
122
123 fn push<'a>(&self, route: impl Into<Cow<'a, str>>) {
124 let route = route.into();
125
126 assert_absolute_path(&route);
127 assert_no_query(&route);
128 assert_no_fragment(&route);
129
130 let location = Location {
131 path: route.to_string().into(),
132 query_str: "".to_string().into(),
133 hash: "".to_string().into(),
134 state: None,
135 id: Some(get_id()),
136 };
137
138 self.inner.borrow_mut().push(location);
139
140 self.notify_callbacks();
141 }
142
143 fn replace<'a>(&self, route: impl Into<Cow<'a, str>>) {
144 let route = route.into();
145
146 assert_absolute_path(&route);
147 assert_no_query(&route);
148 assert_no_fragment(&route);
149
150 let location = Location {
151 path: route.to_string().into(),
152 query_str: "".to_string().into(),
153 hash: "".to_string().into(),
154 state: None,
155 id: Some(get_id()),
156 };
157
158 self.inner.borrow_mut().replace(location);
159
160 self.notify_callbacks();
161 }
162
163 fn push_with_state<'a, T>(&self, route: impl Into<Cow<'a, str>>, state: T)
164 where
165 T: 'static,
166 {
167 let route = route.into();
168
169 assert_absolute_path(&route);
170 assert_no_query(&route);
171 assert_no_fragment(&route);
172
173 let location = Location {
174 path: route.to_string().into(),
175 query_str: "".to_string().into(),
176 hash: "".to_string().into(),
177 state: Some(Rc::new(state) as Rc<dyn Any>),
178 id: Some(get_id()),
179 };
180
181 self.inner.borrow_mut().push(location);
182
183 self.notify_callbacks();
184 }
185
186 fn replace_with_state<'a, T>(&self, route: impl Into<Cow<'a, str>>, state: T)
187 where
188 T: 'static,
189 {
190 let route = route.into();
191
192 assert_absolute_path(&route);
193 assert_no_query(&route);
194 assert_no_fragment(&route);
195
196 let location = Location {
197 path: route.to_string().into(),
198 query_str: "".to_string().into(),
199 hash: "".to_string().into(),
200 state: Some(Rc::new(state) as Rc<dyn Any>),
201 id: Some(get_id()),
202 };
203
204 self.inner.borrow_mut().replace(location);
205
206 self.notify_callbacks();
207 }
208
209 #[cfg(feature = "query")]
210 fn push_with_query<'a, Q>(
211 &self,
212 route: impl Into<Cow<'a, str>>,
213 query: Q,
214 ) -> HistoryResult<(), Q::Error>
215 where
216 Q: ToQuery,
217 {
218 let query = query.to_query()?;
219 let route = route.into();
220
221 assert_absolute_path(&route);
222 assert_no_query(&route);
223 assert_no_fragment(&route);
224
225 let location = Location {
226 path: route.to_string().into(),
227 query_str: format!("?{query}").into(),
228 hash: "".to_string().into(),
229 state: None,
230 id: Some(get_id()),
231 };
232
233 self.inner.borrow_mut().push(location);
234
235 self.notify_callbacks();
236
237 Ok(())
238 }
239 #[cfg(feature = "query")]
240 fn replace_with_query<'a, Q>(
241 &self,
242 route: impl Into<Cow<'a, str>>,
243 query: Q,
244 ) -> HistoryResult<(), Q::Error>
245 where
246 Q: ToQuery,
247 {
248 let query = query.to_query()?;
249 let route = route.into();
250
251 assert_absolute_path(&route);
252 assert_no_query(&route);
253 assert_no_fragment(&route);
254
255 let location = Location {
256 path: route.to_string().into(),
257 query_str: format!("?{query}").into(),
258 hash: "".to_string().into(),
259 state: None,
260 id: Some(get_id()),
261 };
262
263 self.inner.borrow_mut().replace(location);
264
265 self.notify_callbacks();
266
267 Ok(())
268 }
269
270 #[cfg(feature = "query")]
271 fn push_with_query_and_state<'a, Q, T>(
272 &self,
273 route: impl Into<Cow<'a, str>>,
274 query: Q,
275 state: T,
276 ) -> HistoryResult<(), Q::Error>
277 where
278 Q: ToQuery,
279 T: 'static,
280 {
281 let query = query.to_query()?;
282 let route = route.into();
283
284 assert_absolute_path(&route);
285 assert_no_query(&route);
286 assert_no_fragment(&route);
287
288 let location = Location {
289 path: route.to_string().into(),
290 query_str: format!("?{query}").into(),
291 hash: "".to_string().into(),
292 state: Some(Rc::new(state) as Rc<dyn Any>),
293 id: Some(get_id()),
294 };
295
296 self.inner.borrow_mut().push(location);
297
298 self.notify_callbacks();
299
300 Ok(())
301 }
302
303 #[cfg(feature = "query")]
304 fn replace_with_query_and_state<'a, Q, T>(
305 &self,
306 route: impl Into<Cow<'a, str>>,
307 query: Q,
308 state: T,
309 ) -> HistoryResult<(), Q::Error>
310 where
311 Q: ToQuery,
312 T: 'static,
313 {
314 let query = query.to_query()?;
315 let route = route.into();
316
317 assert_absolute_path(&route);
318 assert_no_query(&route);
319 assert_no_fragment(&route);
320
321 let location = Location {
322 path: route.to_string().into(),
323 query_str: format!("?{query}").into(),
324 hash: "".to_string().into(),
325 state: Some(Rc::new(state) as Rc<dyn Any>),
326 id: Some(get_id()),
327 };
328
329 self.inner.borrow_mut().replace(location);
330
331 self.notify_callbacks();
332
333 Ok(())
334 }
335
336 fn listen<CB>(&self, callback: CB) -> HistoryListener
337 where
338 CB: Fn() + 'static,
339 {
340 let cb = Rc::new(callback) as Rc<dyn Fn()>;
342
343 self.callbacks.borrow_mut().push(Rc::downgrade(&cb));
344
345 HistoryListener { _listener: cb }
346 }
347
348 fn location(&self) -> Location {
349 self.inner.borrow().current()
350 }
351}
352
353impl MemoryHistory {
354 pub fn new() -> Self {
356 Self::default()
357 }
358
359 pub fn with_entries<'a>(entries: impl IntoIterator<Item = impl Into<Cow<'a, str>>>) -> Self {
361 let self_ = Self::new();
362
363 for (index, entry) in entries.into_iter().enumerate() {
364 if index == 0 {
365 self_.replace(entry);
366 } else {
367 self_.push(entry);
368 }
369 }
370
371 self_
372 }
373
374 fn notify_callbacks(&self) {
375 crate::utils::notify_callbacks(self.callbacks.clone());
376 }
377}