1use super::*;
4
5pub(crate) static LOCAL_STORAGE: spin::Mutex<alloc::collections::BTreeMap<String, String>> =
8 spin::Mutex::new(alloc::collections::BTreeMap::new());
9pub(crate) static SESSION_STORAGE: spin::Mutex<alloc::collections::BTreeMap<String, String>> =
10 spin::Mutex::new(alloc::collections::BTreeMap::new());
11pub(crate) static LS_LOADED: core::sync::atomic::AtomicBool =
12 core::sync::atomic::AtomicBool::new(false);
13pub(crate) static COOKIES: spin::Mutex<alloc::collections::BTreeMap<String, String>> =
18 spin::Mutex::new(alloc::collections::BTreeMap::new());
19
20pub(crate) const LS_FILENAME: &str = "__localStorage__";
21
22pub(crate) use crate::os_lib::web_storage::{ls_deserialize, ls_serialize};
23
24pub(crate) fn ls_ensure_loaded() {
26 if LS_LOADED.load(core::sync::atomic::Ordering::Relaxed) {
27 return;
28 }
29 LS_LOADED.store(true, core::sync::atomic::Ordering::Relaxed);
30 if !crate::kernel::fs::is_mounted() {
31 return;
32 }
33 if let Some(data) = crate::kernel::fs::read_file(LS_FILENAME) {
34 *LOCAL_STORAGE.lock() = ls_deserialize(&data);
35 }
36}
37
38pub(crate) fn ls_flush() {
40 if !crate::kernel::fs::is_mounted() {
41 return;
42 }
43 let data = ls_serialize(&LOCAL_STORAGE.lock());
44 let _fs_guard = crate::kernel::fs::FS_LOCK.lock();
54 let fs = crate::kernel::fs::get_fs();
55 if let Err(e) = fs.delete_file(LS_FILENAME) {
60 if e != "file not found" {
62 crate::warn!("[JS] localStorage: failed to remove old store: {}", e);
63 }
64 }
65 if !data.is_empty() {
66 if let Err(e) = fs.save_file(LS_FILENAME, &data, "system") {
67 crate::error!("[JS] localStorage: failed to persist ({} bytes): {}", data.len(), e);
68 }
69 }
70}
71
72pub fn cookie_string() -> String {
74 COOKIES
75 .lock()
76 .iter()
77 .map(|(k, v)| alloc::format!("{}={}", k, v))
78 .collect::<alloc::vec::Vec<_>>()
79 .join("; ")
80}
81
82pub fn cookie_set(s: &str) {
86 let first = s.split(';').next().unwrap_or("");
87 if let Some(eq) = first.find('=') {
88 let name = first.get(..eq).unwrap_or("").trim();
89 let value = first.get(eq + 1..).unwrap_or("").trim();
90 if !name.is_empty() {
91 COOKIES
92 .lock()
93 .insert(String::from(name), String::from(value));
94 }
95 }
96}
97
98pub(crate) fn cookie_store_get(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
102 let name = arg(a, 0).to_js_string();
103 let value = COOKIES.lock().get(&name).cloned();
104 let result = match value {
105 Some(v) => {
106 let o = Obj::plain();
107 o.borrow_mut().props.insert("name".into(), Value::str(&name));
108 o.borrow_mut().props.insert("value".into(), Value::str(v));
109 Value::Object(o)
110 }
111 None => Value::Null,
112 };
113 Ok(resolved_promise(it, result))
114}
115pub(crate) fn cookie_store_get_all(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
117 let filter = if a.is_empty() { None } else { Some(arg(a, 0).to_js_string()) };
118 let items: Vec<Value> = COOKIES
119 .lock()
120 .iter()
121 .filter(|(k, _)| filter.as_ref().is_none_or(|f| *f == **k))
122 .map(|(k, v)| {
123 let o = Obj::plain();
124 o.borrow_mut().props.insert("name".into(), Value::str(k));
125 o.borrow_mut().props.insert("value".into(), Value::str(v));
126 Value::Object(o)
127 })
128 .collect();
129 Ok(resolved_promise(it, Value::Object(Obj::array(items))))
130}
131pub(crate) fn cookie_store_set(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
134 let first = arg(a, 0);
135 let (name, value) = if let Value::Object(_) = &first {
136 (
137 obj_prop(&first, "name").map(|v| v.to_js_string()).unwrap_or_default(),
138 obj_prop(&first, "value").map(|v| v.to_js_string()).unwrap_or_default(),
139 )
140 } else {
141 (first.to_js_string(), arg(a, 1).to_js_string())
142 };
143 if !name.is_empty() {
144 COOKIES.lock().insert(name, value);
145 }
146 Ok(resolved_promise(it, Value::Undefined))
147}
148pub(crate) fn cookie_store_delete(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
150 let name = arg(a, 0).to_js_string();
151 COOKIES.lock().remove(&name);
152 Ok(resolved_promise(it, Value::Undefined))
153}
154
155pub(crate) fn storage_map(tag: &str) -> &'static spin::Mutex<alloc::collections::BTreeMap<String, String>> {
156 if tag == "storage:session" {
157 &SESSION_STORAGE
158 } else {
159 &LOCAL_STORAGE
160 }
161}
162pub fn storage_host_keys(kind: &ObjKind) -> Option<Vec<String>> {
166 if let ObjKind::Host(t) = kind {
167 if t.starts_with("storage:") {
168 if t.as_str() != "storage:session" {
169 ls_ensure_loaded();
170 }
171 return Some(storage_map(t).lock().keys().cloned().collect());
172 }
173 }
174 None
175}
176pub fn dataset_host_keys(kind: &ObjKind, dom: &super::super::dom_bridge::DomBridge) -> Option<Vec<String>> {
183 dataset_host_entries(kind, dom).map(|entries| entries.into_iter().map(|(k, _)| k).collect())
184}
185pub fn dataset_host_entries(
188 kind: &ObjKind,
189 dom: &super::super::dom_bridge::DomBridge,
190) -> Option<Vec<(String, String)>> {
191 if let ObjKind::Host(t) = kind {
192 if let Some(rest) = t.strip_prefix("dataset:") {
193 let idx: usize = rest.parse().ok()?;
194 return Some(
195 dom.attr_names(idx)
196 .into_iter()
197 .filter_map(|n| {
198 crate::os_lib::dom_dataset::data_attr_to_camel(&n).map(|camel| {
199 let val = dom.get_attr(idx, &n).unwrap_or_default();
200 (camel, val)
201 })
202 })
203 .collect(),
204 );
205 }
206 }
207 None
208}
209pub(crate) fn data_attr_to_camel(name: &str) -> String {
211 let full = if name.starts_with("data-") {
212 name.to_string()
213 } else {
214 alloc::format!("data-{}", name)
215 };
216 crate::os_lib::dom_dataset::data_attr_to_camel(&full).unwrap_or_else(|| name.to_string())
217}
218pub(crate) fn this_storage_tag(this: &Value) -> Option<String> {
219 if let Value::Object(o) = this {
220 if let ObjKind::Host(t) = &o.borrow().kind {
221 if t.starts_with("storage:") {
222 return Some(t.clone());
223 }
224 }
225 }
226 None
227}
228pub(crate) fn storage_get_item(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
229 if let Some(tag) = this_storage_tag(&this) {
230 if tag != "storage:session" {
231 ls_ensure_loaded();
232 }
233 let k = arg(a, 0).to_js_string();
234 return Ok(match storage_map(&tag).lock().get(&k) {
235 Some(v) => Value::str(v.clone()),
236 None => Value::Null,
237 });
238 }
239 Ok(Value::Null)
240}
241pub(crate) fn storage_set_item(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
242 if let Some(tag) = this_storage_tag(&this) {
243 if tag != "storage:session" {
244 ls_ensure_loaded();
245 }
246 storage_map(&tag)
247 .lock()
248 .insert(arg(a, 0).to_js_string(), arg(a, 1).to_js_string());
249 if tag != "storage:session" {
250 ls_flush();
251 }
252 }
253 Ok(Value::Undefined)
254}
255pub(crate) fn storage_remove_item(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
256 if let Some(tag) = this_storage_tag(&this) {
257 if tag != "storage:session" {
258 ls_ensure_loaded();
259 }
260 storage_map(&tag).lock().remove(&arg(a, 0).to_js_string());
261 if tag != "storage:session" {
262 ls_flush();
263 }
264 }
265 Ok(Value::Undefined)
266}
267pub(crate) fn storage_clear(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
268 if let Some(tag) = this_storage_tag(&this) {
269 storage_map(&tag).lock().clear();
270 if tag != "storage:session" {
271 ls_flush();
272 }
273 }
274 Ok(Value::Undefined)
275}
276pub(crate) fn storage_key(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
277 if let Some(tag) = this_storage_tag(&this) {
278 if tag != "storage:session" {
279 ls_ensure_loaded();
280 }
281 let i = arg(a, 0).to_number() as usize;
282 return Ok(match storage_map(&tag).lock().keys().nth(i) {
283 Some(k) => Value::str(k.clone()),
284 None => Value::Null,
285 });
286 }
287 Ok(Value::Null)
288}
289
290pub fn storage_get_prop(tag: &str, key: &str) -> Value {
292 if tag != "storage:session" {
293 ls_ensure_loaded();
294 }
295 match key {
296 "getItem" => nv("getItem", storage_get_item),
297 "setItem" => nv("setItem", storage_set_item),
298 "removeItem" => nv("removeItem", storage_remove_item),
299 "clear" => nv("clear", storage_clear),
300 "key" => nv("key", storage_key),
301 "length" => Value::Number(storage_map(tag).lock().len() as f64),
302 _ => match storage_map(tag).lock().get(key) {
303 Some(v) => Value::str(v.clone()),
304 None => Value::Undefined,
305 },
306 }
307}
308pub fn storage_remove_prop(tag: &str, key: &str) {
313 if tag != "storage:session" {
314 ls_ensure_loaded();
315 }
316 storage_map(tag).lock().remove(key);
317 if tag != "storage:session" {
318 ls_flush();
319 }
320}
321pub fn storage_set_prop(tag: &str, key: &str, val: &Value) {
322 if key != "length" {
323 if tag != "storage:session" {
324 ls_ensure_loaded();
325 }
326 storage_map(tag)
327 .lock()
328 .insert(String::from(key), val.to_js_string());
329 if tag != "storage:session" {
330 ls_flush();
331 }
332 }
333}
334
335pub(crate) fn parse_query(q: &str) -> Vec<(String, String)> {
339 let q = q.strip_prefix('?').unwrap_or(q);
340 q.split('&')
341 .filter(|s| !s.is_empty())
342 .map(|pair| match pair.split_once('=') {
343 Some((k, v)) => (percent_decode(k, true), percent_decode(v, true)),
344 None => (percent_decode(pair, true), String::new()),
345 })
346 .collect()
347}
348pub(crate) fn form_url_encode(s: &str) -> String {
350 let mut out = String::new();
351 for b in s.as_bytes() {
352 match *b {
353 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
354 out.push(*b as char);
355 }
356 b' ' => out.push('+'),
357 _ => out.push_str(&alloc::format!("%{:02X}", b)),
358 }
359 }
360 out
361}
362pub(crate) fn build_query(pairs: &[(String, String)]) -> String {
363 let parts: Vec<String> = pairs
364 .iter()
365 .map(|(k, v)| format!("{}={}", form_url_encode(k), form_url_encode(v)))
366 .collect();
367 parts.join("&")
368}
369pub fn usp_entry_count(this: &Value) -> usize {
374 parse_query(&usp_query(this)).len()
375}
376pub(crate) fn usp_query(this: &Value) -> String {
377 if let Value::Object(o) = this {
378 return o
379 .borrow()
380 .props
381 .get("_query")
382 .map(|v| v.to_js_string())
383 .unwrap_or_default();
384 }
385 String::new()
386}
387pub(crate) fn usp_set_query(this: &Value, q: &str) {
388 if let Value::Object(o) = this {
389 let parent = {
390 let mut b = o.borrow_mut();
391 b.props.insert(String::from("_query"), Value::str(q));
392 b.props.get("_parent_url").cloned()
393 };
394 if let Some(Value::Object(po)) = parent {
395 let search_val = if q.is_empty() {
396 String::new()
397 } else {
398 alloc::format!("?{}", q)
399 };
400 po.borrow_mut()
401 .props
402 .insert(String::from("search"), Value::str(search_val));
403 }
404 }
405}
406pub(crate) fn usp_get(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
407 let k = arg(a, 0).to_js_string();
408 let pairs = parse_query(&usp_query(&this));
409 Ok(pairs
410 .iter()
411 .find(|(pk, _)| *pk == k)
412 .map(|(_, v)| Value::str(v.clone()))
413 .unwrap_or(Value::Null))
414}
415pub(crate) fn usp_get_all(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
416 let k = arg(a, 0).to_js_string();
417 let items: Vec<Value> = parse_query(&usp_query(&this))
418 .into_iter()
419 .filter(|(pk, _)| *pk == k)
420 .map(|(_, v)| Value::str(v))
421 .collect();
422 Ok(Value::Object(Obj::array(items)))
423}
424pub(crate) fn usp_has(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
429 let k = arg(a, 0).to_js_string();
430 let entries = parse_query(&usp_query(&this));
431 let found = if matches!(arg(a, 1), Value::Undefined) {
432 entries.iter().any(|(pk, _)| *pk == k)
433 } else {
434 let v = arg(a, 1).to_js_string();
435 entries.iter().any(|(pk, pv)| *pk == k && *pv == v)
436 };
437 Ok(Value::Bool(found))
438}
439pub(crate) fn usp_append(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
440 let mut pairs = parse_query(&usp_query(&this));
441 pairs.push((arg(a, 0).to_js_string(), arg(a, 1).to_js_string()));
442 usp_set_query(&this, &build_query(&pairs));
443 Ok(Value::Undefined)
444}
445pub(crate) fn usp_set(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
446 let k = arg(a, 0).to_js_string();
447 let mut pairs: Vec<(String, String)> = parse_query(&usp_query(&this))
448 .into_iter()
449 .filter(|(pk, _)| *pk != k)
450 .collect();
451 pairs.push((k, arg(a, 1).to_js_string()));
452 usp_set_query(&this, &build_query(&pairs));
453 Ok(Value::Undefined)
454}
455pub(crate) fn usp_delete(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
459 let k = arg(a, 0).to_js_string();
460 let value_filter = if matches!(arg(a, 1), Value::Undefined) {
461 None
462 } else {
463 Some(arg(a, 1).to_js_string())
464 };
465 let pairs: Vec<(String, String)> = parse_query(&usp_query(&this))
466 .into_iter()
467 .filter(|(pk, pv)| match &value_filter {
468 Some(v) => !(*pk == k && pv == v),
469 None => *pk != k,
470 })
471 .collect();
472 usp_set_query(&this, &build_query(&pairs));
473 Ok(Value::Undefined)
474}
475pub(crate) fn usp_to_string(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
476 Ok(Value::str(usp_query(&this)))
477}
478pub(crate) fn usp_sort(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
481 let mut pairs = parse_query(&usp_query(&this));
482 pairs.sort_by(|a, b| a.0.cmp(&b.0));
483 usp_set_query(&this, &build_query(&pairs));
484 Ok(Value::Undefined)
485}
486pub(crate) fn usp_iterator(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
489 let items: Vec<Value> = parse_query(&usp_query(&this))
490 .into_iter()
491 .map(|(k, v)| Value::Object(Obj::array(alloc::vec![Value::str(k), Value::str(v)])))
492 .collect();
493 Ok(Value::Object(make_iterator(items)))
494}
495pub(crate) fn url_search_params_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
505 let init = arg(a, 0);
506 let q = match &init {
507 Value::Undefined | Value::Null => String::new(),
508 Value::Object(o) => {
509 let is_array = matches!(&o.borrow().kind, ObjKind::Array(_));
510 if is_array {
511 let pairs: alloc::vec::Vec<(String, String)> = this_items(&init)
512 .iter()
513 .filter_map(|item| {
514 let inner = this_items(item);
515 if inner.len() >= 2 {
516 Some((inner[0].to_js_string(), inner[1].to_js_string()))
517 } else {
518 None
519 }
520 })
521 .collect();
522 build_query(&pairs)
523 } else if let Some(existing) = o.borrow().props.get("_query") {
524 existing.to_js_string()
526 } else {
527 let pairs: alloc::vec::Vec<(String, String)> = o
528 .borrow()
529 .props
530 .iter()
531 .map(|(k, v)| (k.clone(), v.to_js_string()))
532 .collect();
533 build_query(&pairs)
534 }
535 }
536 other => other.to_js_string(),
537 };
538 let o = Obj::plain();
539 {
540 let mut b = o.borrow_mut();
541 b.props
542 .insert("_query".into(), Value::str(build_query(&parse_query(&q))));
543 b.props.insert("_is_usp".into(), Value::Bool(true));
546 b.props.insert("sort".into(), nv("sort", usp_sort));
548 b.props.insert("get".into(), nv("get", usp_get));
549 b.props.insert("getAll".into(), nv("getAll", usp_get_all));
550 b.props.insert("has".into(), nv("has", usp_has));
551 b.props.insert("append".into(), nv("append", usp_append));
552 b.props.insert("set".into(), nv("set", usp_set));
553 b.props.insert("delete".into(), nv("delete", usp_delete));
554 b.props.insert("entries".into(), nv("entries", fd_entries));
558 b.props.insert("keys".into(), nv("keys", fd_keys));
559 b.props.insert("values".into(), nv("values", fd_values));
560 b.props.insert("forEach".into(), nv("forEach", fd_for_each));
561 b.props.insert(
567 "Symbol(Symbol.iterator)".into(),
568 nv("[Symbol.iterator]", usp_iterator),
569 );
570 b.props
571 .insert("toString".into(), nv("toString", usp_to_string));
572 }
573 Ok(Value::Object(o))
574}
575
576pub(crate) fn fd_entries(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
580 let items: Vec<Value> = parse_query(&usp_query(&this))
581 .into_iter()
582 .map(|(k, v)| Value::Object(Obj::array(alloc::vec![Value::str(k), Value::str(v)])))
583 .collect();
584 Ok(Value::Object(Obj::array(items)))
585}
586pub(crate) fn fd_keys(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
587 let items: Vec<Value> = parse_query(&usp_query(&this))
588 .into_iter()
589 .map(|(k, _)| Value::str(k))
590 .collect();
591 Ok(Value::Object(Obj::array(items)))
592}
593pub(crate) fn fd_values(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
594 let items: Vec<Value> = parse_query(&usp_query(&this))
595 .into_iter()
596 .map(|(_, v)| Value::str(v))
597 .collect();
598 Ok(Value::Object(Obj::array(items)))
599}
600pub(crate) fn fd_for_each(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
601 let cb = arg(a, 0);
602 if matches!(cb, Value::Object(_)) {
603 for (k, v) in parse_query(&usp_query(&this)) {
604 it.call_value(
605 &cb,
606 Value::Undefined,
607 &[Value::str(v), Value::str(k), this.clone()],
608 )?;
609 }
610 }
611 Ok(Value::Undefined)
612}
613
614pub(crate) fn collect_form_data(it: &Interp, form_idx: usize) -> Vec<(String, String)> {
620 let dom = it.dom.borrow();
621 let mut out: Vec<(String, String)> = Vec::new();
622 let mut stack: Vec<usize> = alloc::vec![0usize];
630 let mut order: Vec<usize> = Vec::new();
631 while let Some(i) = stack.pop() {
632 order.push(i);
633 if let Some(n) = dom.nodes.get(i) {
634 for &c in n.children.iter().rev() {
636 stack.push(c);
637 }
638 }
639 }
640 for &i in order.iter() {
641 let n = match dom.nodes.get(i) {
642 Some(n) => n,
643 None => continue,
644 };
645 if n.is_text || i == form_idx {
646 continue;
647 }
648 let tag = n.tag.as_str();
649 if tag != "input" && tag != "select" && tag != "textarea" {
650 continue;
651 }
652 if dom.associated_form(i) != Some(form_idx) {
653 continue;
654 }
655 if dom.is_disabled(i) {
660 continue;
661 }
662 let name = dom.get_attr(i, "name").unwrap_or_default();
663 if name.is_empty() {
664 continue;
665 }
666 match tag {
667 "input" => {
668 let ty = dom.get_attr(i, "type").unwrap_or_default().to_lowercase();
669 match ty.as_str() {
670 "submit" | "button" | "reset" | "image" | "file" => { }
671 "checkbox" | "radio" => {
672 if dom.has_attr(i, "checked") {
673 let v = dom
674 .get_attr(i, "value")
675 .filter(|s| !s.is_empty())
676 .unwrap_or_else(|| String::from("on"));
677 out.push((name, v));
678 }
679 }
680 _ => {
681 out.push((name, dom.get_attr(i, "value").unwrap_or_default()));
682 }
683 }
684 }
685 "textarea" => {
686 let v = dom
687 .get_attr(i, "value")
688 .unwrap_or_else(|| n.initial_text.clone());
689 out.push((name, v));
690 }
691 "select" => {
692 if dom.has_attr(i, "multiple") {
702 for c in dom.select_options(i) {
703 if dom.has_attr(c, "selected") {
704 let v = dom.get_attr(c, "value").unwrap_or_else(|| {
705 dom.nodes.get(c).map(|cn| cn.initial_text.clone()).unwrap_or_default()
706 });
707 out.push((name.clone(), v));
708 }
709 }
710 }
711 else if let Some(v) = dom.get_attr(i, "value").filter(|s| !s.is_empty()) {
713 out.push((name, v));
714 } else {
715 let opts = dom.select_options(i);
727 let chosen = opts
728 .iter()
729 .find(|&&o| dom.has_attr(o, "selected"))
730 .or_else(|| opts.first());
731 if let Some(&oi) = chosen {
732 let v = dom.get_attr(oi, "value").unwrap_or_else(|| {
733 dom.nodes
734 .get(oi)
735 .map(|cn| cn.initial_text.clone())
736 .unwrap_or_default()
737 });
738 out.push((name, v));
739 }
740 }
741 }
742 _ => {}
743 }
744 }
745 out
746}
747
748pub(crate) fn form_data_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
749 let pairs = match this_dom_idx(&arg(a, 0)) {
751 Some(form_idx) => collect_form_data(it, form_idx),
752 None => Vec::new(),
753 };
754 let o = Obj::plain();
755 {
756 let mut b = o.borrow_mut();
757 b.props
758 .insert("_query".into(), Value::str(build_query(&pairs)));
759 b.props.insert("get".into(), nv("get", usp_get));
760 b.props.insert("getAll".into(), nv("getAll", usp_get_all));
761 b.props.insert("has".into(), nv("has", usp_has));
762 b.props.insert("append".into(), nv("append", usp_append));
763 b.props.insert("set".into(), nv("set", usp_set));
764 b.props.insert("delete".into(), nv("delete", usp_delete));
765 b.props.insert("entries".into(), nv("entries", fd_entries));
766 b.props.insert("keys".into(), nv("keys", fd_keys));
767 b.props.insert("values".into(), nv("values", fd_values));
768 b.props.insert("forEach".into(), nv("forEach", fd_for_each));
769 b.props.insert(
772 "Symbol(Symbol.iterator)".into(),
773 nv("[Symbol.iterator]", usp_iterator),
774 );
775 b.props
776 .insert("toString".into(), nv("toString", usp_to_string));
777 }
778 Ok(Value::Object(o))
779}
780
781pub(crate) fn url_can_parse(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
792 let raw = arg(a, 0).to_js_string();
793 let base = match arg(a, 1) {
794 Value::Undefined => String::new(),
795 v => v.to_js_string(),
796 };
797 let href = if base.is_empty() { raw } else { resolve_url(&base, &raw) };
798 Ok(Value::Bool(has_url_scheme(&href)))
799}
800pub(crate) fn url_parse(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
807 let raw = arg(a, 0).to_js_string();
808 let base = match arg(a, 1) {
809 Value::Undefined => String::new(),
810 v => v.to_js_string(),
811 };
812 let href = if base.is_empty() { raw } else { resolve_url(&base, &raw) };
813 if !has_url_scheme(&href) {
814 return Ok(Value::Null);
815 }
816 url_ctor(it, Value::Undefined, a)
817}
818pub(crate) fn has_url_scheme(s: &str) -> bool {
819 if !s.contains(':') {
820 return false;
821 }
822 let mut chars = s.chars().take_while(|&c| c != ':');
825 match chars.next() {
826 Some(c) if c.is_ascii_alphabetic() => {}
827 _ => return false,
828 }
829 chars.all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.')
830}
831pub(crate) fn url_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
832 let raw = arg(a, 0).to_js_string();
833 let base = if matches!(arg(a, 1), Value::Undefined) {
834 String::new()
835 } else {
836 arg(a, 1).to_js_string()
837 };
838 let href = if base.is_empty() {
839 raw
840 } else {
841 resolve_url(&base, &raw)
842 };
843 let comps = location_components(&href);
844 let search = comps
845 .iter()
846 .find(|(k, _)| *k == "search")
847 .map(|(_, v)| v.clone())
848 .unwrap_or_default();
849 let sp = url_search_params_ctor(it, Value::Undefined, &[Value::str(search)])?;
850 let o = Obj::plain();
851 if let Value::Object(sp_obj) = &sp {
852 sp_obj.borrow_mut().props.insert(String::from("_parent_url"), Value::Object(o.clone()));
853 }
854 {
855 let mut b = o.borrow_mut();
856 for (k, v) in &comps {
857 b.props.insert(String::from(*k), Value::str(v.clone()));
858 }
859 b.props.insert("searchParams".into(), sp);
860 b.props
864 .insert("toString".into(), nv("toString", url_to_string));
865 b.props
872 .insert("toJSON".into(), nv("toJSON", url_to_string));
873 b.accessors.insert(
880 String::from("href"),
881 super::super::value::Accessor {
882 get: Some(nv("href", url_to_string)),
883 set: Some(nv("href", url_set_href)),
891 },
892 );
893 b.props.shift_remove("href");
894 for (key, getter, setter) in [
904 ("pathname", nv("pathname", url_get_pathname) , nv("pathname", url_set_pathname)),
905 ("search", nv("search", url_get_search), nv("search", url_set_search)),
906 ("hash", nv("hash", url_get_hash), nv("hash", url_set_hash)),
907 ] {
908 b.accessors.insert(
914 String::from(key),
915 super::super::value::Accessor { get: Some(getter), set: Some(setter) },
916 );
917 }
918 for (key, getter, setter) in [
925 ("protocol", nv("protocol", url_get_protocol), nv("protocol", url_set_protocol)),
926 ("host", nv("host", url_get_host), nv("host", url_set_host)),
927 ("hostname", nv("hostname", url_get_hostname), nv("hostname", url_set_hostname)),
928 ("port", nv("port", url_get_port), nv("port", url_set_port)),
929 ] {
930 b.accessors.insert(
931 String::from(key),
932 super::super::value::Accessor { get: Some(getter), set: Some(setter) },
933 );
934 }
935 }
936 Ok(Value::Object(o))
937}
938
939fn url_rebuild_host_and_origin(b: &mut Obj) {
944 let protocol = b.props.get("protocol").map(|v| v.to_js_string()).unwrap_or_default();
945 let hostname = b.props.get("hostname").map(|v| v.to_js_string()).unwrap_or_default();
946 let port = b.props.get("port").map(|v| v.to_js_string()).unwrap_or_default();
947 let host = if port.is_empty() { hostname.clone() } else { alloc::format!("{}:{}", hostname, port) };
948 let origin = if protocol.is_empty() { String::new() } else { alloc::format!("{}//{}", protocol, host) };
949 b.props.insert("host".into(), Value::str(host));
950 b.props.insert("origin".into(), Value::str(origin));
951}
952pub(crate) fn url_get_protocol(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
953 Ok(url_prop_or_empty(&this, "protocol"))
954}
955pub(crate) fn url_set_protocol(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
956 if let Value::Object(o) = &this {
957 let raw = arg(a, 0).to_js_string();
958 let v = if raw.ends_with(':') { raw } else { alloc::format!("{}:", raw) };
959 let mut b = o.borrow_mut();
960 b.props.insert("protocol".into(), Value::str(v));
961 url_rebuild_host_and_origin(&mut b);
962 }
963 Ok(Value::Undefined)
964}
965pub(crate) fn url_get_hostname(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
966 Ok(url_prop_or_empty(&this, "hostname"))
967}
968pub(crate) fn url_set_hostname(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
969 if let Value::Object(o) = &this {
970 let raw = arg(a, 0).to_js_string();
971 let mut b = o.borrow_mut();
972 b.props.insert("hostname".into(), Value::str(raw));
973 url_rebuild_host_and_origin(&mut b);
974 }
975 Ok(Value::Undefined)
976}
977pub(crate) fn url_get_port(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
978 Ok(url_prop_or_empty(&this, "port"))
979}
980pub(crate) fn url_set_port(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
981 if let Value::Object(o) = &this {
982 let raw = arg(a, 0).to_js_string();
983 let mut b = o.borrow_mut();
984 b.props.insert("port".into(), Value::str(raw));
985 url_rebuild_host_and_origin(&mut b);
986 }
987 Ok(Value::Undefined)
988}
989pub(crate) fn url_get_host(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
990 Ok(url_prop_or_empty(&this, "host"))
991}
992pub(crate) fn url_set_host(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
995 if let Value::Object(o) = &this {
996 let raw = arg(a, 0).to_js_string();
997 let (hostname, port) = match raw.split_once(':') {
998 Some((h, p)) => (String::from(h), String::from(p)),
999 None => (raw, String::new()),
1000 };
1001 let mut b = o.borrow_mut();
1002 b.props.insert("hostname".into(), Value::str(hostname));
1003 b.props.insert("port".into(), Value::str(port));
1004 url_rebuild_host_and_origin(&mut b);
1005 }
1006 Ok(Value::Undefined)
1007}
1008
1009fn url_prop_or_empty(this: &Value, key: &str) -> Value {
1010 if let Value::Object(o) = this {
1011 if let Some(v) = o.borrow().props.get(key) {
1012 return v.clone();
1013 }
1014 }
1015 Value::str("")
1016}
1017pub(crate) fn url_get_pathname(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1018 Ok(url_prop_or_empty(&this, "pathname"))
1019}
1020pub(crate) fn url_set_pathname(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1021 if let Value::Object(o) = &this {
1022 let raw = arg(a, 0).to_js_string();
1023 let v = if raw.is_empty() || raw.starts_with('/') {
1024 raw
1025 } else {
1026 alloc::format!("/{}", raw)
1027 };
1028 o.borrow_mut().props.insert("pathname".into(), Value::str(v));
1029 }
1030 Ok(Value::Undefined)
1031}
1032pub(crate) fn url_get_hash(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1033 Ok(url_prop_or_empty(&this, "hash"))
1034}
1035pub(crate) fn url_set_hash(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1036 if let Value::Object(o) = &this {
1037 let raw = arg(a, 0).to_js_string();
1038 let frag = raw.strip_prefix('#').unwrap_or(&raw);
1039 let v = if frag.is_empty() { String::new() } else { alloc::format!("#{}", frag) };
1040 o.borrow_mut().props.insert("hash".into(), Value::str(v));
1041 }
1042 Ok(Value::Undefined)
1043}
1044pub(crate) fn url_get_search(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1055 if let Value::Object(o) = &this {
1056 let sp = o.borrow().props.get("searchParams").cloned();
1057 if let Some(sp_val) = sp {
1058 let query = usp_to_string(it, sp_val, a)?.to_js_string();
1059 let v = if query.is_empty() { String::new() } else { alloc::format!("?{}", query) };
1060 return Ok(Value::str(v));
1061 }
1062 }
1063 Ok(url_prop_or_empty(&this, "search"))
1064}
1065pub(crate) fn url_set_search(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1069 if let Value::Object(o) = &this {
1070 let raw = arg(a, 0).to_js_string();
1071 let query = raw.strip_prefix('?').unwrap_or(&raw).to_string();
1072 let sp = url_search_params_ctor(it, Value::Undefined, &[Value::str(query.clone())])?;
1073 let v = if query.is_empty() { String::new() } else { alloc::format!("?{}", query) };
1074 let mut b = o.borrow_mut();
1075 b.props.insert("search".into(), Value::str(v));
1076 b.props.insert("searchParams".into(), sp);
1077 }
1078 Ok(Value::Undefined)
1079}
1080
1081pub(crate) fn url_set_href(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1085 if let Value::Object(o) = &this {
1086 let new_href = arg(a, 0).to_js_string();
1087 let comps = location_components(&new_href);
1088 let search = comps
1089 .iter()
1090 .find(|(k, _)| *k == "search")
1091 .map(|(_, v)| v.clone())
1092 .unwrap_or_default();
1093 let sp = url_search_params_ctor(it, Value::Undefined, &[Value::str(search)])?;
1094 let mut b = o.borrow_mut();
1095 for (k, v) in &comps {
1096 b.props.insert(String::from(*k), Value::str(v.clone()));
1097 }
1098 b.props.insert("searchParams".into(), sp);
1099 }
1100 Ok(Value::Undefined)
1101}
1102
1103pub(crate) fn url_to_string(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1108 if let Value::Object(o) = &this {
1109 let (protocol, host, pathname, hash, sp) = {
1110 let b = o.borrow();
1111 (
1112 b.props.get("protocol").map(|v| v.to_js_string()).unwrap_or_default(),
1113 b.props.get("host").map(|v| v.to_js_string()).unwrap_or_default(),
1114 b.props.get("pathname").map(|v| v.to_js_string()).unwrap_or_default(),
1115 b.props.get("hash").map(|v| v.to_js_string()).unwrap_or_default(),
1116 b.props.get("searchParams").cloned(),
1117 )
1118 };
1119 let query = match sp {
1120 Some(sp_val) => usp_to_string(it, sp_val, a)?.to_js_string(),
1121 None => String::new(),
1122 };
1123 let mut s = alloc::format!("{}//{}{}", protocol, host, pathname);
1124 if !query.is_empty() {
1125 s.push('?');
1126 s.push_str(&query);
1127 }
1128 s.push_str(&hash);
1129 return Ok(Value::str(s));
1130 }
1131 Ok(Value::str(String::new()))
1132}
1133
1134pub(crate) fn url_pattern_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1137 let arg0 = arg(a, 0);
1138 let mut pattern_pathname = String::from("*");
1139 let mut pattern_hostname = String::from("*");
1140 let mut pattern_protocol = String::from("*");
1141
1142 match &arg0 {
1143 Value::Object(o) => {
1144 let b = o.borrow();
1145 if let Some(p) = b.props.get("pathname") {
1146 pattern_pathname = p.to_js_string();
1147 }
1148 if let Some(h) = b.props.get("hostname") {
1149 pattern_hostname = h.to_js_string();
1150 }
1151 if let Some(pr) = b.props.get("protocol") {
1152 pattern_protocol = pr.to_js_string();
1153 }
1154 }
1155 _ => {
1156 let pat_str = arg0.to_js_string();
1157 if !pat_str.is_empty() {
1158 if pat_str.contains("://") {
1159 let href = pat_str;
1160 if let Some((proto, rest)) = href.split_once("://") {
1161 pattern_protocol = proto.to_string();
1162 match rest.split_once('/') {
1163 Some((host, path)) => {
1164 pattern_hostname = host.to_string();
1165 pattern_pathname = alloc::format!("/{}", path);
1166 }
1167 None => {
1168 pattern_hostname = rest.to_string();
1169 pattern_pathname = String::from("/");
1170 }
1171 }
1172 }
1173 } else {
1174 pattern_pathname = pat_str;
1175 }
1176 }
1177 }
1178 }
1179
1180 let o = Obj::plain();
1181 {
1182 let mut b = o.borrow_mut();
1183 b.props.insert("pathname".into(), Value::str(pattern_pathname));
1184 b.props.insert("hostname".into(), Value::str(pattern_hostname));
1185 b.props.insert("protocol".into(), Value::str(pattern_protocol));
1186 b.props.insert("test".into(), nv("test", url_pattern_test));
1187 b.props.insert("exec".into(), nv("exec", url_pattern_exec));
1188 }
1189 Ok(Value::Object(o))
1190}
1191
1192fn match_pathname_pattern(pattern: &str, target: &str) -> Option<Vec<(String, String)>> {
1202 if pattern == "*" || pattern.is_empty() {
1203 return Some(Vec::new());
1204 }
1205 if pattern == target {
1206 return Some(Vec::new());
1207 }
1208 if pattern.contains('*') && !pattern.contains(':') {
1209 let parts: Vec<&str> = pattern.split('*').collect();
1210 if parts.len() == 2 && target.starts_with(parts[0]) && target.ends_with(parts[1]) {
1211 return Some(Vec::new());
1212 }
1213 return None;
1214 }
1215 let pat_segs: Vec<&str> = pattern.split('/').collect();
1216 let tgt_segs: Vec<&str> = target.split('/').collect();
1217 if pat_segs.len() != tgt_segs.len() {
1218 return None;
1219 }
1220 let mut groups = Vec::new();
1221 for (p, t) in pat_segs.iter().zip(tgt_segs.iter()) {
1222 if let Some(name) = p.strip_prefix(':') {
1223 groups.push((name.to_string(), t.to_string()));
1224 } else if *p == "*" {
1225 } else if p != t {
1227 return None;
1228 }
1229 }
1230 Some(groups)
1231}
1232
1233fn match_pattern_part(pattern: &str, target: &str) -> bool {
1234 if pattern == "*" || pattern.is_empty() {
1235 return true;
1236 }
1237 if pattern == target {
1238 return true;
1239 }
1240 if pattern.starts_with(':') || pattern == "*" {
1241 return true;
1242 }
1243 if pattern.contains('*') {
1244 let parts: Vec<&str> = pattern.split('*').collect();
1245 if parts.len() == 2 {
1246 return target.starts_with(parts[0]) && target.ends_with(parts[1]);
1247 }
1248 }
1249 false
1250}
1251
1252pub(crate) fn url_pattern_test(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1253 let res = url_pattern_exec(it, this, a)?;
1254 Ok(Value::Bool(!matches!(res, Value::Null)))
1255}
1256
1257pub(crate) fn url_pattern_exec(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1258 let input = arg(a, 0);
1259 let (pat_pathname, pat_hostname, pat_protocol) = match &this {
1260 Value::Object(o) => {
1261 let b = o.borrow();
1262 (
1263 b.props.get("pathname").map(|v| v.to_js_string()).unwrap_or_else(|| "*".into()),
1264 b.props.get("hostname").map(|v| v.to_js_string()).unwrap_or_else(|| "*".into()),
1265 b.props.get("protocol").map(|v| v.to_js_string()).unwrap_or_else(|| "*".into()),
1266 )
1267 }
1268 _ => return Ok(Value::Null),
1269 };
1270
1271 let mut target_pathname = String::new();
1272 let mut target_hostname = String::new();
1273 let mut target_protocol = String::new();
1274
1275 match &input {
1276 Value::Object(o) => {
1277 let b = o.borrow();
1278 target_pathname = b.props.get("pathname").map(|v| v.to_js_string()).unwrap_or_default();
1279 target_hostname = b.props.get("hostname").map(|v| v.to_js_string()).unwrap_or_default();
1280 target_protocol = b.props.get("protocol").map(|v| v.to_js_string()).unwrap_or_default();
1281 }
1282 _ => {
1283 let url_str = input.to_js_string();
1284 if url_str.contains("://") {
1285 if let Some((proto, rest)) = url_str.split_once("://") {
1286 target_protocol = proto.to_string();
1287 match rest.split_once('/') {
1288 Some((host, path)) => {
1289 target_hostname = host.to_string();
1290 target_pathname = alloc::format!("/{}", path);
1291 }
1292 None => {
1293 target_hostname = rest.to_string();
1294 target_pathname = String::from("/");
1295 }
1296 }
1297 }
1298 } else {
1299 target_pathname = url_str;
1300 }
1301 }
1302 }
1303
1304 let path_match = match_pathname_pattern(&pat_pathname, &target_pathname);
1305 let match_host = match_pattern_part(&pat_hostname, &target_hostname);
1306 let match_proto = match_pattern_part(&pat_protocol, &target_protocol);
1307
1308 if let (Some(groups), true, true) = (path_match, match_host, match_proto) {
1309 let result_obj = Obj::plain();
1310 {
1311 let mut b = result_obj.borrow_mut();
1312 let pathname_obj = Obj::plain();
1313 {
1314 let mut pb = pathname_obj.borrow_mut();
1315 pb.props.insert("input".into(), Value::str(target_pathname));
1316 let groups_obj = Obj::plain();
1317 {
1318 let mut gb = groups_obj.borrow_mut();
1319 for (name, value) in &groups {
1320 gb.props.insert(name.clone(), Value::str(value.clone()));
1321 }
1322 }
1323 pb.props.insert("groups".into(), Value::Object(groups_obj));
1324 }
1325 b.props.insert("pathname".into(), Value::Object(pathname_obj));
1326 }
1327 Ok(Value::Object(result_obj))
1328 } else {
1329 Ok(Value::Null)
1330 }
1331}
1332