1use std::cell::RefCell;
2
3use wasm_bindgen::JsCast;
4
5pub(crate) fn strip_slash_suffix(path: &str) -> &str {
6 path.strip_suffix('/').unwrap_or(path)
7}
8
9static BASE_URL_LOADED: std::sync::Once = std::sync::Once::new();
10thread_local! {
11 static BASE_URL: RefCell<Option<String>> = const { RefCell::new(None) };
12}
13
14#[doc(hidden)]
17pub fn base_url() -> Option<String> {
18 BASE_URL_LOADED.call_once(|| {
19 BASE_URL.with(|val| {
20 *val.borrow_mut() = fetch_base_url();
21 })
22 });
23 BASE_URL.with(|it| it.borrow().as_ref().map(|it| it.to_string()))
24}
25
26pub fn fetch_base_url() -> Option<String> {
27 match gloo::utils::document().query_selector("base[href]") {
28 Ok(Some(base)) => {
29 let base = base.unchecked_into::<web_sys::HtmlBaseElement>().href();
30
31 let url = web_sys::Url::new(&base).unwrap();
32 let base = url.pathname();
33
34 let base = if base != "/" {
35 strip_slash_suffix(&base)
36 } else {
37 return None;
38 };
39
40 Some(base.to_string())
41 }
42 _ => None,
43 }
44}
45
46#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
47pub fn compose_path(pathname: &str, query: &str) -> Option<String> {
48 gloo::utils::window()
49 .location()
50 .href()
51 .ok()
52 .and_then(|base| web_sys::Url::new_with_base(pathname, &base).ok())
53 .map(|url| {
54 url.set_search(query);
55 format!("{}{}", url.pathname(), url.search())
56 })
57}
58
59#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
60pub fn compose_path(pathname: &str, query: &str) -> Option<String> {
61 let query = query.trim();
62
63 if !query.is_empty() {
64 Some(format!("{pathname}?{query}"))
65 } else {
66 Some(pathname.to_owned())
67 }
68}
69
70#[cfg(all(
72 test,
73 target_arch = "wasm32",
74 any(target_os = "unknown", target_os = "none")
75))]
76mod tests {
77 use gloo::utils::document;
78 use wasm_bindgen_test::wasm_bindgen_test as test;
79 use yew_router::prelude::*;
80 use yew_router::utils::*;
81
82 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
83
84 #[derive(Debug, Clone, Copy, PartialEq, Routable)]
85 enum Routes {
86 #[at("/")]
87 Home,
88 #[at("/no")]
89 No,
90 #[at("/404")]
91 NotFound,
92 }
93
94 #[test]
95 fn test_base_url() {
96 document().head().unwrap().set_inner_html(r#""#);
97
98 assert_eq!(fetch_base_url(), None);
99
100 document()
101 .head()
102 .unwrap()
103 .set_inner_html(r#"<base href="/base/">"#);
104 assert_eq!(fetch_base_url(), Some("/base".to_string()));
105
106 document()
107 .head()
108 .unwrap()
109 .set_inner_html(r#"<base href="/base">"#);
110 assert_eq!(fetch_base_url(), Some("/base".to_string()));
111 }
112
113 #[test]
114 fn test_compose_path() {
115 assert_eq!(compose_path("/home", ""), Some("/home".to_string()));
116 assert_eq!(
117 compose_path("/path/to", "foo=bar"),
118 Some("/path/to?foo=bar".to_string())
119 );
120 assert_eq!(
121 compose_path("/events", "from=2019&to=2021"),
122 Some("/events?from=2019&to=2021".to_string())
123 );
124 }
125}