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) fn ls_serialize(map: &alloc::collections::BTreeMap<String, String>) -> alloc::vec::Vec<u8> {
24 let mut out = alloc::vec::Vec::new();
25 for (k, v) in map.iter() {
26 let kb = k.as_bytes();
27 let vb = v.as_bytes();
28 out.extend_from_slice(&(kb.len() as u32).to_le_bytes());
29 out.extend_from_slice(kb);
30 out.extend_from_slice(&(vb.len() as u32).to_le_bytes());
31 out.extend_from_slice(vb);
32 }
33 out
34}
35
36pub(crate) fn ls_deserialize(data: &[u8]) -> alloc::collections::BTreeMap<String, String> {
38 let mut map = alloc::collections::BTreeMap::new();
39 let mut pos = 0usize;
40 while pos + 8 <= data.len() {
41 let klen = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
42 as usize;
43 pos += 4;
44 if pos + klen > data.len() {
45 break;
46 }
47 let k = alloc::string::String::from_utf8_lossy(&data[pos..pos + klen]).into_owned();
48 pos += klen;
49 if pos + 4 > data.len() {
50 break;
51 }
52 let vlen = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
53 as usize;
54 pos += 4;
55 if pos + vlen > data.len() {
56 break;
57 }
58 let v = alloc::string::String::from_utf8_lossy(&data[pos..pos + vlen]).into_owned();
59 pos += vlen;
60 map.insert(k, v);
61 }
62 map
63}
64
65pub(crate) fn ls_ensure_loaded() {
67 if LS_LOADED.load(core::sync::atomic::Ordering::Relaxed) {
68 return;
69 }
70 LS_LOADED.store(true, core::sync::atomic::Ordering::Relaxed);
71 if !crate::kernel::fs::is_mounted() {
72 return;
73 }
74 if let Some(data) = crate::kernel::fs::read_file(LS_FILENAME) {
75 *LOCAL_STORAGE.lock() = ls_deserialize(&data);
76 }
77}
78
79pub(crate) fn ls_flush() {
81 if !crate::kernel::fs::is_mounted() {
82 return;
83 }
84 let data = ls_serialize(&LOCAL_STORAGE.lock());
85 let _fs_guard = crate::kernel::fs::FS_LOCK.lock();
95 let fs = crate::kernel::fs::get_fs();
96 if let Err(e) = fs.delete_file(LS_FILENAME) {
101 if e != "file not found" {
103 crate::warn!("[JS] localStorage: failed to remove old store: {}", e);
104 }
105 }
106 if !data.is_empty() {
107 if let Err(e) = fs.save_file(LS_FILENAME, &data, "system") {
108 crate::error!("[JS] localStorage: failed to persist ({} bytes): {}", data.len(), e);
109 }
110 }
111}
112
113pub fn cookie_string() -> String {
115 COOKIES
116 .lock()
117 .iter()
118 .map(|(k, v)| alloc::format!("{}={}", k, v))
119 .collect::<alloc::vec::Vec<_>>()
120 .join("; ")
121}
122
123pub fn cookie_set(s: &str) {
127 let first = s.split(';').next().unwrap_or("");
128 if let Some(eq) = first.find('=') {
129 let name = first.get(..eq).unwrap_or("").trim();
130 let value = first.get(eq + 1..).unwrap_or("").trim();
131 if !name.is_empty() {
132 COOKIES
133 .lock()
134 .insert(String::from(name), String::from(value));
135 }
136 }
137}
138
139pub(crate) fn cookie_store_get(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
143 let name = arg(a, 0).to_js_string();
144 let value = COOKIES.lock().get(&name).cloned();
145 let result = match value {
146 Some(v) => {
147 let o = Obj::plain();
148 o.borrow_mut().props.insert("name".into(), Value::str(&name));
149 o.borrow_mut().props.insert("value".into(), Value::str(v));
150 Value::Object(o)
151 }
152 None => Value::Null,
153 };
154 Ok(resolved_promise(it, result))
155}
156pub(crate) fn cookie_store_get_all(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
158 let filter = if a.is_empty() { None } else { Some(arg(a, 0).to_js_string()) };
159 let items: Vec<Value> = COOKIES
160 .lock()
161 .iter()
162 .filter(|(k, _)| filter.as_ref().is_none_or(|f| *f == **k))
163 .map(|(k, v)| {
164 let o = Obj::plain();
165 o.borrow_mut().props.insert("name".into(), Value::str(k));
166 o.borrow_mut().props.insert("value".into(), Value::str(v));
167 Value::Object(o)
168 })
169 .collect();
170 Ok(resolved_promise(it, Value::Object(Obj::array(items))))
171}
172pub(crate) fn cookie_store_set(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
175 let first = arg(a, 0);
176 let (name, value) = if let Value::Object(_) = &first {
177 (
178 obj_prop(&first, "name").map(|v| v.to_js_string()).unwrap_or_default(),
179 obj_prop(&first, "value").map(|v| v.to_js_string()).unwrap_or_default(),
180 )
181 } else {
182 (first.to_js_string(), arg(a, 1).to_js_string())
183 };
184 if !name.is_empty() {
185 COOKIES.lock().insert(name, value);
186 }
187 Ok(resolved_promise(it, Value::Undefined))
188}
189pub(crate) fn cookie_store_delete(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
191 let name = arg(a, 0).to_js_string();
192 COOKIES.lock().remove(&name);
193 Ok(resolved_promise(it, Value::Undefined))
194}
195
196pub(crate) fn storage_map(tag: &str) -> &'static spin::Mutex<alloc::collections::BTreeMap<String, String>> {
197 if tag == "storage:session" {
198 &SESSION_STORAGE
199 } else {
200 &LOCAL_STORAGE
201 }
202}
203pub fn storage_host_keys(kind: &ObjKind) -> Option<Vec<String>> {
207 if let ObjKind::Host(t) = kind {
208 if t.starts_with("storage:") {
209 if t.as_str() != "storage:session" {
210 ls_ensure_loaded();
211 }
212 return Some(storage_map(t).lock().keys().cloned().collect());
213 }
214 }
215 None
216}
217pub fn dataset_host_keys(kind: &ObjKind, dom: &super::super::dom_bridge::DomBridge) -> Option<Vec<String>> {
224 dataset_host_entries(kind, dom).map(|entries| entries.into_iter().map(|(k, _)| k).collect())
225}
226pub fn dataset_host_entries(
229 kind: &ObjKind,
230 dom: &super::super::dom_bridge::DomBridge,
231) -> Option<Vec<(String, String)>> {
232 if let ObjKind::Host(t) = kind {
233 if let Some(rest) = t.strip_prefix("dataset:") {
234 let idx: usize = rest.parse().ok()?;
235 return Some(
236 dom.attr_names(idx)
237 .into_iter()
238 .filter_map(|n| {
239 n.strip_prefix("data-").map(|s| {
240 let camel = data_attr_to_camel(s);
241 let val = dom.get_attr(idx, &n).unwrap_or_default();
242 (camel, val)
243 })
244 })
245 .collect(),
246 );
247 }
248 }
249 None
250}
251pub(crate) fn data_attr_to_camel(name: &str) -> String {
253 let mut out = String::new();
254 let mut upper_next = false;
255 for c in name.chars() {
256 if c == '-' {
257 upper_next = true;
258 } else if upper_next {
259 out.extend(c.to_uppercase());
260 upper_next = false;
261 } else {
262 out.push(c);
263 }
264 }
265 out
266}
267pub(crate) fn this_storage_tag(this: &Value) -> Option<String> {
268 if let Value::Object(o) = this {
269 if let ObjKind::Host(t) = &o.borrow().kind {
270 if t.starts_with("storage:") {
271 return Some(t.clone());
272 }
273 }
274 }
275 None
276}
277pub(crate) fn storage_get_item(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
278 if let Some(tag) = this_storage_tag(&this) {
279 if tag != "storage:session" {
280 ls_ensure_loaded();
281 }
282 let k = arg(a, 0).to_js_string();
283 return Ok(match storage_map(&tag).lock().get(&k) {
284 Some(v) => Value::str(v.clone()),
285 None => Value::Null,
286 });
287 }
288 Ok(Value::Null)
289}
290pub(crate) fn storage_set_item(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
291 if let Some(tag) = this_storage_tag(&this) {
292 if tag != "storage:session" {
293 ls_ensure_loaded();
294 }
295 storage_map(&tag)
296 .lock()
297 .insert(arg(a, 0).to_js_string(), arg(a, 1).to_js_string());
298 if tag != "storage:session" {
299 ls_flush();
300 }
301 }
302 Ok(Value::Undefined)
303}
304pub(crate) fn storage_remove_item(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
305 if let Some(tag) = this_storage_tag(&this) {
306 if tag != "storage:session" {
307 ls_ensure_loaded();
308 }
309 storage_map(&tag).lock().remove(&arg(a, 0).to_js_string());
310 if tag != "storage:session" {
311 ls_flush();
312 }
313 }
314 Ok(Value::Undefined)
315}
316pub(crate) fn storage_clear(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
317 if let Some(tag) = this_storage_tag(&this) {
318 storage_map(&tag).lock().clear();
319 if tag != "storage:session" {
320 ls_flush();
321 }
322 }
323 Ok(Value::Undefined)
324}
325pub(crate) fn storage_key(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
326 if let Some(tag) = this_storage_tag(&this) {
327 if tag != "storage:session" {
328 ls_ensure_loaded();
329 }
330 let i = arg(a, 0).to_number() as usize;
331 return Ok(match storage_map(&tag).lock().keys().nth(i) {
332 Some(k) => Value::str(k.clone()),
333 None => Value::Null,
334 });
335 }
336 Ok(Value::Null)
337}
338
339pub fn storage_get_prop(tag: &str, key: &str) -> Value {
341 if tag != "storage:session" {
342 ls_ensure_loaded();
343 }
344 match key {
345 "getItem" => nv("getItem", storage_get_item),
346 "setItem" => nv("setItem", storage_set_item),
347 "removeItem" => nv("removeItem", storage_remove_item),
348 "clear" => nv("clear", storage_clear),
349 "key" => nv("key", storage_key),
350 "length" => Value::Number(storage_map(tag).lock().len() as f64),
351 _ => match storage_map(tag).lock().get(key) {
352 Some(v) => Value::str(v.clone()),
353 None => Value::Undefined,
354 },
355 }
356}
357pub fn storage_remove_prop(tag: &str, key: &str) {
362 if tag != "storage:session" {
363 ls_ensure_loaded();
364 }
365 storage_map(tag).lock().remove(key);
366 if tag != "storage:session" {
367 ls_flush();
368 }
369}
370pub fn storage_set_prop(tag: &str, key: &str, val: &Value) {
371 if key != "length" {
372 if tag != "storage:session" {
373 ls_ensure_loaded();
374 }
375 storage_map(tag)
376 .lock()
377 .insert(String::from(key), val.to_js_string());
378 if tag != "storage:session" {
379 ls_flush();
380 }
381 }
382}
383
384pub(crate) fn parse_query(q: &str) -> Vec<(String, String)> {
388 let q = q.strip_prefix('?').unwrap_or(q);
389 q.split('&')
390 .filter(|s| !s.is_empty())
391 .map(|pair| match pair.split_once('=') {
392 Some((k, v)) => (percent_decode(k, true), percent_decode(v, true)),
393 None => (percent_decode(pair, true), String::new()),
394 })
395 .collect()
396}
397pub(crate) fn form_url_encode(s: &str) -> String {
399 let mut out = String::new();
400 for b in s.as_bytes() {
401 match *b {
402 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
403 out.push(*b as char);
404 }
405 b' ' => out.push('+'),
406 _ => out.push_str(&alloc::format!("%{:02X}", b)),
407 }
408 }
409 out
410}
411pub(crate) fn build_query(pairs: &[(String, String)]) -> String {
412 let parts: Vec<String> = pairs
413 .iter()
414 .map(|(k, v)| format!("{}={}", form_url_encode(k), form_url_encode(v)))
415 .collect();
416 parts.join("&")
417}
418pub fn usp_entry_count(this: &Value) -> usize {
423 parse_query(&usp_query(this)).len()
424}
425pub(crate) fn usp_query(this: &Value) -> String {
426 if let Value::Object(o) = this {
427 return o
428 .borrow()
429 .props
430 .get("_query")
431 .map(|v| v.to_js_string())
432 .unwrap_or_default();
433 }
434 String::new()
435}
436pub(crate) fn usp_set_query(this: &Value, q: &str) {
437 if let Value::Object(o) = this {
438 let parent = {
439 let mut b = o.borrow_mut();
440 b.props.insert(String::from("_query"), Value::str(q));
441 b.props.get("_parent_url").cloned()
442 };
443 if let Some(Value::Object(po)) = parent {
444 let search_val = if q.is_empty() {
445 String::new()
446 } else {
447 alloc::format!("?{}", q)
448 };
449 po.borrow_mut()
450 .props
451 .insert(String::from("search"), Value::str(search_val));
452 }
453 }
454}
455pub(crate) fn usp_get(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
456 let k = arg(a, 0).to_js_string();
457 let pairs = parse_query(&usp_query(&this));
458 Ok(pairs
459 .iter()
460 .find(|(pk, _)| *pk == k)
461 .map(|(_, v)| Value::str(v.clone()))
462 .unwrap_or(Value::Null))
463}
464pub(crate) fn usp_get_all(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
465 let k = arg(a, 0).to_js_string();
466 let items: Vec<Value> = parse_query(&usp_query(&this))
467 .into_iter()
468 .filter(|(pk, _)| *pk == k)
469 .map(|(_, v)| Value::str(v))
470 .collect();
471 Ok(Value::Object(Obj::array(items)))
472}
473pub(crate) fn usp_has(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
478 let k = arg(a, 0).to_js_string();
479 let entries = parse_query(&usp_query(&this));
480 let found = if matches!(arg(a, 1), Value::Undefined) {
481 entries.iter().any(|(pk, _)| *pk == k)
482 } else {
483 let v = arg(a, 1).to_js_string();
484 entries.iter().any(|(pk, pv)| *pk == k && *pv == v)
485 };
486 Ok(Value::Bool(found))
487}
488pub(crate) fn usp_append(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
489 let mut pairs = parse_query(&usp_query(&this));
490 pairs.push((arg(a, 0).to_js_string(), arg(a, 1).to_js_string()));
491 usp_set_query(&this, &build_query(&pairs));
492 Ok(Value::Undefined)
493}
494pub(crate) fn usp_set(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
495 let k = arg(a, 0).to_js_string();
496 let mut pairs: Vec<(String, String)> = parse_query(&usp_query(&this))
497 .into_iter()
498 .filter(|(pk, _)| *pk != k)
499 .collect();
500 pairs.push((k, arg(a, 1).to_js_string()));
501 usp_set_query(&this, &build_query(&pairs));
502 Ok(Value::Undefined)
503}
504pub(crate) fn usp_delete(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
508 let k = arg(a, 0).to_js_string();
509 let value_filter = if matches!(arg(a, 1), Value::Undefined) {
510 None
511 } else {
512 Some(arg(a, 1).to_js_string())
513 };
514 let pairs: Vec<(String, String)> = parse_query(&usp_query(&this))
515 .into_iter()
516 .filter(|(pk, pv)| match &value_filter {
517 Some(v) => !(*pk == k && pv == v),
518 None => *pk != k,
519 })
520 .collect();
521 usp_set_query(&this, &build_query(&pairs));
522 Ok(Value::Undefined)
523}
524pub(crate) fn usp_to_string(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
525 Ok(Value::str(usp_query(&this)))
526}
527pub(crate) fn usp_sort(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
530 let mut pairs = parse_query(&usp_query(&this));
531 pairs.sort_by(|a, b| a.0.cmp(&b.0));
532 usp_set_query(&this, &build_query(&pairs));
533 Ok(Value::Undefined)
534}
535pub(crate) fn usp_iterator(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
538 let items: Vec<Value> = parse_query(&usp_query(&this))
539 .into_iter()
540 .map(|(k, v)| Value::Object(Obj::array(alloc::vec![Value::str(k), Value::str(v)])))
541 .collect();
542 Ok(Value::Object(make_iterator(items)))
543}
544pub(crate) fn url_search_params_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
554 let init = arg(a, 0);
555 let q = match &init {
556 Value::Undefined | Value::Null => String::new(),
557 Value::Object(o) => {
558 let is_array = matches!(&o.borrow().kind, ObjKind::Array(_));
559 if is_array {
560 let pairs: alloc::vec::Vec<(String, String)> = this_items(&init)
561 .iter()
562 .filter_map(|item| {
563 let inner = this_items(item);
564 if inner.len() >= 2 {
565 Some((inner[0].to_js_string(), inner[1].to_js_string()))
566 } else {
567 None
568 }
569 })
570 .collect();
571 build_query(&pairs)
572 } else if let Some(existing) = o.borrow().props.get("_query") {
573 existing.to_js_string()
575 } else {
576 let pairs: alloc::vec::Vec<(String, String)> = o
577 .borrow()
578 .props
579 .iter()
580 .map(|(k, v)| (k.clone(), v.to_js_string()))
581 .collect();
582 build_query(&pairs)
583 }
584 }
585 other => other.to_js_string(),
586 };
587 let o = Obj::plain();
588 {
589 let mut b = o.borrow_mut();
590 b.props
591 .insert("_query".into(), Value::str(build_query(&parse_query(&q))));
592 b.props.insert("_is_usp".into(), Value::Bool(true));
595 b.props.insert("sort".into(), nv("sort", usp_sort));
597 b.props.insert("get".into(), nv("get", usp_get));
598 b.props.insert("getAll".into(), nv("getAll", usp_get_all));
599 b.props.insert("has".into(), nv("has", usp_has));
600 b.props.insert("append".into(), nv("append", usp_append));
601 b.props.insert("set".into(), nv("set", usp_set));
602 b.props.insert("delete".into(), nv("delete", usp_delete));
603 b.props.insert("entries".into(), nv("entries", fd_entries));
607 b.props.insert("keys".into(), nv("keys", fd_keys));
608 b.props.insert("values".into(), nv("values", fd_values));
609 b.props.insert("forEach".into(), nv("forEach", fd_for_each));
610 b.props.insert(
616 "Symbol(Symbol.iterator)".into(),
617 nv("[Symbol.iterator]", usp_iterator),
618 );
619 b.props
620 .insert("toString".into(), nv("toString", usp_to_string));
621 }
622 Ok(Value::Object(o))
623}
624
625pub(crate) fn fd_entries(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
629 let items: Vec<Value> = parse_query(&usp_query(&this))
630 .into_iter()
631 .map(|(k, v)| Value::Object(Obj::array(alloc::vec![Value::str(k), Value::str(v)])))
632 .collect();
633 Ok(Value::Object(Obj::array(items)))
634}
635pub(crate) fn fd_keys(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
636 let items: Vec<Value> = parse_query(&usp_query(&this))
637 .into_iter()
638 .map(|(k, _)| Value::str(k))
639 .collect();
640 Ok(Value::Object(Obj::array(items)))
641}
642pub(crate) fn fd_values(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
643 let items: Vec<Value> = parse_query(&usp_query(&this))
644 .into_iter()
645 .map(|(_, v)| Value::str(v))
646 .collect();
647 Ok(Value::Object(Obj::array(items)))
648}
649pub(crate) fn fd_for_each(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
650 let cb = arg(a, 0);
651 if matches!(cb, Value::Object(_)) {
652 for (k, v) in parse_query(&usp_query(&this)) {
653 it.call_value(
654 &cb,
655 Value::Undefined,
656 &[Value::str(v), Value::str(k), this.clone()],
657 )?;
658 }
659 }
660 Ok(Value::Undefined)
661}
662
663pub(crate) fn collect_form_data(it: &Interp, form_idx: usize) -> Vec<(String, String)> {
669 let dom = it.dom.borrow();
670 let mut out: Vec<(String, String)> = Vec::new();
671 let mut stack: Vec<usize> = alloc::vec![0usize];
679 let mut order: Vec<usize> = Vec::new();
680 while let Some(i) = stack.pop() {
681 order.push(i);
682 if let Some(n) = dom.nodes.get(i) {
683 for &c in n.children.iter().rev() {
685 stack.push(c);
686 }
687 }
688 }
689 for &i in order.iter() {
690 let n = match dom.nodes.get(i) {
691 Some(n) => n,
692 None => continue,
693 };
694 if n.is_text || i == form_idx {
695 continue;
696 }
697 let tag = n.tag.as_str();
698 if tag != "input" && tag != "select" && tag != "textarea" {
699 continue;
700 }
701 if dom.associated_form(i) != Some(form_idx) {
702 continue;
703 }
704 if dom.is_disabled(i) {
709 continue;
710 }
711 let name = dom.get_attr(i, "name").unwrap_or_default();
712 if name.is_empty() {
713 continue;
714 }
715 match tag {
716 "input" => {
717 let ty = dom.get_attr(i, "type").unwrap_or_default().to_lowercase();
718 match ty.as_str() {
719 "submit" | "button" | "reset" | "image" | "file" => { }
720 "checkbox" | "radio" => {
721 if dom.has_attr(i, "checked") {
722 let v = dom
723 .get_attr(i, "value")
724 .filter(|s| !s.is_empty())
725 .unwrap_or_else(|| String::from("on"));
726 out.push((name, v));
727 }
728 }
729 _ => {
730 out.push((name, dom.get_attr(i, "value").unwrap_or_default()));
731 }
732 }
733 }
734 "textarea" => {
735 let v = dom
736 .get_attr(i, "value")
737 .unwrap_or_else(|| n.initial_text.clone());
738 out.push((name, v));
739 }
740 "select" => {
741 if dom.has_attr(i, "multiple") {
751 for c in dom.select_options(i) {
752 if dom.has_attr(c, "selected") {
753 let v = dom.get_attr(c, "value").unwrap_or_else(|| {
754 dom.nodes.get(c).map(|cn| cn.initial_text.clone()).unwrap_or_default()
755 });
756 out.push((name.clone(), v));
757 }
758 }
759 }
760 else if let Some(v) = dom.get_attr(i, "value").filter(|s| !s.is_empty()) {
762 out.push((name, v));
763 } else {
764 let opts = dom.select_options(i);
776 let chosen = opts
777 .iter()
778 .find(|&&o| dom.has_attr(o, "selected"))
779 .or_else(|| opts.first());
780 if let Some(&oi) = chosen {
781 let v = dom.get_attr(oi, "value").unwrap_or_else(|| {
782 dom.nodes
783 .get(oi)
784 .map(|cn| cn.initial_text.clone())
785 .unwrap_or_default()
786 });
787 out.push((name, v));
788 }
789 }
790 }
791 _ => {}
792 }
793 }
794 out
795}
796
797pub(crate) fn form_data_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
798 let pairs = match this_dom_idx(&arg(a, 0)) {
800 Some(form_idx) => collect_form_data(it, form_idx),
801 None => Vec::new(),
802 };
803 let o = Obj::plain();
804 {
805 let mut b = o.borrow_mut();
806 b.props
807 .insert("_query".into(), Value::str(build_query(&pairs)));
808 b.props.insert("get".into(), nv("get", usp_get));
809 b.props.insert("getAll".into(), nv("getAll", usp_get_all));
810 b.props.insert("has".into(), nv("has", usp_has));
811 b.props.insert("append".into(), nv("append", usp_append));
812 b.props.insert("set".into(), nv("set", usp_set));
813 b.props.insert("delete".into(), nv("delete", usp_delete));
814 b.props.insert("entries".into(), nv("entries", fd_entries));
815 b.props.insert("keys".into(), nv("keys", fd_keys));
816 b.props.insert("values".into(), nv("values", fd_values));
817 b.props.insert("forEach".into(), nv("forEach", fd_for_each));
818 b.props.insert(
821 "Symbol(Symbol.iterator)".into(),
822 nv("[Symbol.iterator]", usp_iterator),
823 );
824 b.props
825 .insert("toString".into(), nv("toString", usp_to_string));
826 }
827 Ok(Value::Object(o))
828}
829
830pub(crate) fn url_can_parse(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
841 let raw = arg(a, 0).to_js_string();
842 let base = match arg(a, 1) {
843 Value::Undefined => String::new(),
844 v => v.to_js_string(),
845 };
846 let href = if base.is_empty() { raw } else { resolve_url(&base, &raw) };
847 Ok(Value::Bool(has_url_scheme(&href)))
848}
849pub(crate) fn url_parse(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
856 let raw = arg(a, 0).to_js_string();
857 let base = match arg(a, 1) {
858 Value::Undefined => String::new(),
859 v => v.to_js_string(),
860 };
861 let href = if base.is_empty() { raw } else { resolve_url(&base, &raw) };
862 if !has_url_scheme(&href) {
863 return Ok(Value::Null);
864 }
865 url_ctor(it, Value::Undefined, a)
866}
867pub(crate) fn has_url_scheme(s: &str) -> bool {
868 if !s.contains(':') {
869 return false;
870 }
871 let mut chars = s.chars().take_while(|&c| c != ':');
874 match chars.next() {
875 Some(c) if c.is_ascii_alphabetic() => {}
876 _ => return false,
877 }
878 chars.all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.')
879}
880pub(crate) fn url_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
881 let raw = arg(a, 0).to_js_string();
882 let base = if matches!(arg(a, 1), Value::Undefined) {
883 String::new()
884 } else {
885 arg(a, 1).to_js_string()
886 };
887 let href = if base.is_empty() {
888 raw
889 } else {
890 resolve_url(&base, &raw)
891 };
892 let comps = location_components(&href);
893 let search = comps
894 .iter()
895 .find(|(k, _)| *k == "search")
896 .map(|(_, v)| v.clone())
897 .unwrap_or_default();
898 let sp = url_search_params_ctor(it, Value::Undefined, &[Value::str(search)])?;
899 let o = Obj::plain();
900 if let Value::Object(sp_obj) = &sp {
901 sp_obj.borrow_mut().props.insert(String::from("_parent_url"), Value::Object(o.clone()));
902 }
903 {
904 let mut b = o.borrow_mut();
905 for (k, v) in &comps {
906 b.props.insert(String::from(*k), Value::str(v.clone()));
907 }
908 b.props.insert("searchParams".into(), sp);
909 b.props
913 .insert("toString".into(), nv("toString", url_to_string));
914 b.props
921 .insert("toJSON".into(), nv("toJSON", url_to_string));
922 b.accessors.insert(
929 String::from("href"),
930 super::super::value::Accessor {
931 get: Some(nv("href", url_to_string)),
932 set: Some(nv("href", url_set_href)),
940 },
941 );
942 b.props.shift_remove("href");
943 for (key, getter, setter) in [
953 ("pathname", nv("pathname", url_get_pathname) , nv("pathname", url_set_pathname)),
954 ("search", nv("search", url_get_search), nv("search", url_set_search)),
955 ("hash", nv("hash", url_get_hash), nv("hash", url_set_hash)),
956 ] {
957 b.accessors.insert(
963 String::from(key),
964 super::super::value::Accessor { get: Some(getter), set: Some(setter) },
965 );
966 }
967 for (key, getter, setter) in [
974 ("protocol", nv("protocol", url_get_protocol), nv("protocol", url_set_protocol)),
975 ("host", nv("host", url_get_host), nv("host", url_set_host)),
976 ("hostname", nv("hostname", url_get_hostname), nv("hostname", url_set_hostname)),
977 ("port", nv("port", url_get_port), nv("port", url_set_port)),
978 ] {
979 b.accessors.insert(
980 String::from(key),
981 super::super::value::Accessor { get: Some(getter), set: Some(setter) },
982 );
983 }
984 }
985 Ok(Value::Object(o))
986}
987
988fn url_rebuild_host_and_origin(b: &mut Obj) {
993 let protocol = b.props.get("protocol").map(|v| v.to_js_string()).unwrap_or_default();
994 let hostname = b.props.get("hostname").map(|v| v.to_js_string()).unwrap_or_default();
995 let port = b.props.get("port").map(|v| v.to_js_string()).unwrap_or_default();
996 let host = if port.is_empty() { hostname.clone() } else { alloc::format!("{}:{}", hostname, port) };
997 let origin = if protocol.is_empty() { String::new() } else { alloc::format!("{}//{}", protocol, host) };
998 b.props.insert("host".into(), Value::str(host));
999 b.props.insert("origin".into(), Value::str(origin));
1000}
1001pub(crate) fn url_get_protocol(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1002 Ok(url_prop_or_empty(&this, "protocol"))
1003}
1004pub(crate) fn url_set_protocol(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1005 if let Value::Object(o) = &this {
1006 let raw = arg(a, 0).to_js_string();
1007 let v = if raw.ends_with(':') { raw } else { alloc::format!("{}:", raw) };
1008 let mut b = o.borrow_mut();
1009 b.props.insert("protocol".into(), Value::str(v));
1010 url_rebuild_host_and_origin(&mut b);
1011 }
1012 Ok(Value::Undefined)
1013}
1014pub(crate) fn url_get_hostname(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1015 Ok(url_prop_or_empty(&this, "hostname"))
1016}
1017pub(crate) fn url_set_hostname(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1018 if let Value::Object(o) = &this {
1019 let raw = arg(a, 0).to_js_string();
1020 let mut b = o.borrow_mut();
1021 b.props.insert("hostname".into(), Value::str(raw));
1022 url_rebuild_host_and_origin(&mut b);
1023 }
1024 Ok(Value::Undefined)
1025}
1026pub(crate) fn url_get_port(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1027 Ok(url_prop_or_empty(&this, "port"))
1028}
1029pub(crate) fn url_set_port(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1030 if let Value::Object(o) = &this {
1031 let raw = arg(a, 0).to_js_string();
1032 let mut b = o.borrow_mut();
1033 b.props.insert("port".into(), Value::str(raw));
1034 url_rebuild_host_and_origin(&mut b);
1035 }
1036 Ok(Value::Undefined)
1037}
1038pub(crate) fn url_get_host(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1039 Ok(url_prop_or_empty(&this, "host"))
1040}
1041pub(crate) fn url_set_host(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1044 if let Value::Object(o) = &this {
1045 let raw = arg(a, 0).to_js_string();
1046 let (hostname, port) = match raw.split_once(':') {
1047 Some((h, p)) => (String::from(h), String::from(p)),
1048 None => (raw, String::new()),
1049 };
1050 let mut b = o.borrow_mut();
1051 b.props.insert("hostname".into(), Value::str(hostname));
1052 b.props.insert("port".into(), Value::str(port));
1053 url_rebuild_host_and_origin(&mut b);
1054 }
1055 Ok(Value::Undefined)
1056}
1057
1058fn url_prop_or_empty(this: &Value, key: &str) -> Value {
1059 if let Value::Object(o) = this {
1060 if let Some(v) = o.borrow().props.get(key) {
1061 return v.clone();
1062 }
1063 }
1064 Value::str("")
1065}
1066pub(crate) fn url_get_pathname(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1067 Ok(url_prop_or_empty(&this, "pathname"))
1068}
1069pub(crate) fn url_set_pathname(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1070 if let Value::Object(o) = &this {
1071 let raw = arg(a, 0).to_js_string();
1072 let v = if raw.is_empty() || raw.starts_with('/') {
1073 raw
1074 } else {
1075 alloc::format!("/{}", raw)
1076 };
1077 o.borrow_mut().props.insert("pathname".into(), Value::str(v));
1078 }
1079 Ok(Value::Undefined)
1080}
1081pub(crate) fn url_get_hash(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1082 Ok(url_prop_or_empty(&this, "hash"))
1083}
1084pub(crate) fn url_set_hash(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1085 if let Value::Object(o) = &this {
1086 let raw = arg(a, 0).to_js_string();
1087 let frag = raw.strip_prefix('#').unwrap_or(&raw);
1088 let v = if frag.is_empty() { String::new() } else { alloc::format!("#{}", frag) };
1089 o.borrow_mut().props.insert("hash".into(), Value::str(v));
1090 }
1091 Ok(Value::Undefined)
1092}
1093pub(crate) fn url_get_search(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1104 if let Value::Object(o) = &this {
1105 let sp = o.borrow().props.get("searchParams").cloned();
1106 if let Some(sp_val) = sp {
1107 let query = usp_to_string(it, sp_val, a)?.to_js_string();
1108 let v = if query.is_empty() { String::new() } else { alloc::format!("?{}", query) };
1109 return Ok(Value::str(v));
1110 }
1111 }
1112 Ok(url_prop_or_empty(&this, "search"))
1113}
1114pub(crate) fn url_set_search(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1118 if let Value::Object(o) = &this {
1119 let raw = arg(a, 0).to_js_string();
1120 let query = raw.strip_prefix('?').unwrap_or(&raw).to_string();
1121 let sp = url_search_params_ctor(it, Value::Undefined, &[Value::str(query.clone())])?;
1122 let v = if query.is_empty() { String::new() } else { alloc::format!("?{}", query) };
1123 let mut b = o.borrow_mut();
1124 b.props.insert("search".into(), Value::str(v));
1125 b.props.insert("searchParams".into(), sp);
1126 }
1127 Ok(Value::Undefined)
1128}
1129
1130pub(crate) fn url_set_href(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1134 if let Value::Object(o) = &this {
1135 let new_href = arg(a, 0).to_js_string();
1136 let comps = location_components(&new_href);
1137 let search = comps
1138 .iter()
1139 .find(|(k, _)| *k == "search")
1140 .map(|(_, v)| v.clone())
1141 .unwrap_or_default();
1142 let sp = url_search_params_ctor(it, Value::Undefined, &[Value::str(search)])?;
1143 let mut b = o.borrow_mut();
1144 for (k, v) in &comps {
1145 b.props.insert(String::from(*k), Value::str(v.clone()));
1146 }
1147 b.props.insert("searchParams".into(), sp);
1148 }
1149 Ok(Value::Undefined)
1150}
1151
1152pub(crate) fn url_to_string(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1157 if let Value::Object(o) = &this {
1158 let (protocol, host, pathname, hash, sp) = {
1159 let b = o.borrow();
1160 (
1161 b.props.get("protocol").map(|v| v.to_js_string()).unwrap_or_default(),
1162 b.props.get("host").map(|v| v.to_js_string()).unwrap_or_default(),
1163 b.props.get("pathname").map(|v| v.to_js_string()).unwrap_or_default(),
1164 b.props.get("hash").map(|v| v.to_js_string()).unwrap_or_default(),
1165 b.props.get("searchParams").cloned(),
1166 )
1167 };
1168 let query = match sp {
1169 Some(sp_val) => usp_to_string(it, sp_val, a)?.to_js_string(),
1170 None => String::new(),
1171 };
1172 let mut s = alloc::format!("{}//{}{}", protocol, host, pathname);
1173 if !query.is_empty() {
1174 s.push('?');
1175 s.push_str(&query);
1176 }
1177 s.push_str(&hash);
1178 return Ok(Value::str(s));
1179 }
1180 Ok(Value::str(String::new()))
1181}
1182
1183pub(crate) fn url_pattern_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1186 let arg0 = arg(a, 0);
1187 let mut pattern_pathname = String::from("*");
1188 let mut pattern_hostname = String::from("*");
1189 let mut pattern_protocol = String::from("*");
1190
1191 match &arg0 {
1192 Value::Object(o) => {
1193 let b = o.borrow();
1194 if let Some(p) = b.props.get("pathname") {
1195 pattern_pathname = p.to_js_string();
1196 }
1197 if let Some(h) = b.props.get("hostname") {
1198 pattern_hostname = h.to_js_string();
1199 }
1200 if let Some(pr) = b.props.get("protocol") {
1201 pattern_protocol = pr.to_js_string();
1202 }
1203 }
1204 _ => {
1205 let pat_str = arg0.to_js_string();
1206 if !pat_str.is_empty() {
1207 if pat_str.contains("://") {
1208 let href = pat_str;
1209 if let Some((proto, rest)) = href.split_once("://") {
1210 pattern_protocol = proto.to_string();
1211 match rest.split_once('/') {
1212 Some((host, path)) => {
1213 pattern_hostname = host.to_string();
1214 pattern_pathname = alloc::format!("/{}", path);
1215 }
1216 None => {
1217 pattern_hostname = rest.to_string();
1218 pattern_pathname = String::from("/");
1219 }
1220 }
1221 }
1222 } else {
1223 pattern_pathname = pat_str;
1224 }
1225 }
1226 }
1227 }
1228
1229 let o = Obj::plain();
1230 {
1231 let mut b = o.borrow_mut();
1232 b.props.insert("pathname".into(), Value::str(pattern_pathname));
1233 b.props.insert("hostname".into(), Value::str(pattern_hostname));
1234 b.props.insert("protocol".into(), Value::str(pattern_protocol));
1235 b.props.insert("test".into(), nv("test", url_pattern_test));
1236 b.props.insert("exec".into(), nv("exec", url_pattern_exec));
1237 }
1238 Ok(Value::Object(o))
1239}
1240
1241fn match_pathname_pattern(pattern: &str, target: &str) -> Option<Vec<(String, String)>> {
1251 if pattern == "*" || pattern.is_empty() {
1252 return Some(Vec::new());
1253 }
1254 if pattern == target {
1255 return Some(Vec::new());
1256 }
1257 if pattern.contains('*') && !pattern.contains(':') {
1258 let parts: Vec<&str> = pattern.split('*').collect();
1259 if parts.len() == 2 && target.starts_with(parts[0]) && target.ends_with(parts[1]) {
1260 return Some(Vec::new());
1261 }
1262 return None;
1263 }
1264 let pat_segs: Vec<&str> = pattern.split('/').collect();
1265 let tgt_segs: Vec<&str> = target.split('/').collect();
1266 if pat_segs.len() != tgt_segs.len() {
1267 return None;
1268 }
1269 let mut groups = Vec::new();
1270 for (p, t) in pat_segs.iter().zip(tgt_segs.iter()) {
1271 if let Some(name) = p.strip_prefix(':') {
1272 groups.push((name.to_string(), t.to_string()));
1273 } else if *p == "*" {
1274 } else if p != t {
1276 return None;
1277 }
1278 }
1279 Some(groups)
1280}
1281
1282fn match_pattern_part(pattern: &str, target: &str) -> bool {
1283 if pattern == "*" || pattern.is_empty() {
1284 return true;
1285 }
1286 if pattern == target {
1287 return true;
1288 }
1289 if pattern.starts_with(':') || pattern == "*" {
1290 return true;
1291 }
1292 if pattern.contains('*') {
1293 let parts: Vec<&str> = pattern.split('*').collect();
1294 if parts.len() == 2 {
1295 return target.starts_with(parts[0]) && target.ends_with(parts[1]);
1296 }
1297 }
1298 false
1299}
1300
1301pub(crate) fn url_pattern_test(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1302 let res = url_pattern_exec(it, this, a)?;
1303 Ok(Value::Bool(!matches!(res, Value::Null)))
1304}
1305
1306pub(crate) fn url_pattern_exec(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1307 let input = arg(a, 0);
1308 let (pat_pathname, pat_hostname, pat_protocol) = match &this {
1309 Value::Object(o) => {
1310 let b = o.borrow();
1311 (
1312 b.props.get("pathname").map(|v| v.to_js_string()).unwrap_or_else(|| "*".into()),
1313 b.props.get("hostname").map(|v| v.to_js_string()).unwrap_or_else(|| "*".into()),
1314 b.props.get("protocol").map(|v| v.to_js_string()).unwrap_or_else(|| "*".into()),
1315 )
1316 }
1317 _ => return Ok(Value::Null),
1318 };
1319
1320 let mut target_pathname = String::new();
1321 let mut target_hostname = String::new();
1322 let mut target_protocol = String::new();
1323
1324 match &input {
1325 Value::Object(o) => {
1326 let b = o.borrow();
1327 target_pathname = b.props.get("pathname").map(|v| v.to_js_string()).unwrap_or_default();
1328 target_hostname = b.props.get("hostname").map(|v| v.to_js_string()).unwrap_or_default();
1329 target_protocol = b.props.get("protocol").map(|v| v.to_js_string()).unwrap_or_default();
1330 }
1331 _ => {
1332 let url_str = input.to_js_string();
1333 if url_str.contains("://") {
1334 if let Some((proto, rest)) = url_str.split_once("://") {
1335 target_protocol = proto.to_string();
1336 match rest.split_once('/') {
1337 Some((host, path)) => {
1338 target_hostname = host.to_string();
1339 target_pathname = alloc::format!("/{}", path);
1340 }
1341 None => {
1342 target_hostname = rest.to_string();
1343 target_pathname = String::from("/");
1344 }
1345 }
1346 }
1347 } else {
1348 target_pathname = url_str;
1349 }
1350 }
1351 }
1352
1353 let path_match = match_pathname_pattern(&pat_pathname, &target_pathname);
1354 let match_host = match_pattern_part(&pat_hostname, &target_hostname);
1355 let match_proto = match_pattern_part(&pat_protocol, &target_protocol);
1356
1357 if let (Some(groups), true, true) = (path_match, match_host, match_proto) {
1358 let result_obj = Obj::plain();
1359 {
1360 let mut b = result_obj.borrow_mut();
1361 let pathname_obj = Obj::plain();
1362 {
1363 let mut pb = pathname_obj.borrow_mut();
1364 pb.props.insert("input".into(), Value::str(target_pathname));
1365 let groups_obj = Obj::plain();
1366 {
1367 let mut gb = groups_obj.borrow_mut();
1368 for (name, value) in &groups {
1369 gb.props.insert(name.clone(), Value::str(value.clone()));
1370 }
1371 }
1372 pb.props.insert("groups".into(), Value::Object(groups_obj));
1373 }
1374 b.props.insert("pathname".into(), Value::Object(pathname_obj));
1375 }
1376 Ok(Value::Object(result_obj))
1377 } else {
1378 Ok(Value::Null)
1379 }
1380}
1381