1use super::*;
4
5pub(crate) fn string_from_char_code(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
15 let units: Vec<u16> = a
16 .iter()
17 .map(|v| (v.to_number() as i64 as u32 & 0xFFFF) as u16)
18 .collect();
19 Ok(Value::str(String::from_utf16_lossy(&units)))
20}
21pub(crate) fn string_from_code_point(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
25 let mut s = String::new();
26 for v in a {
27 let n = v.to_number();
28 if !n.is_finite() || n < 0.0 || n > 0x10FFFF as f64 || libm::floor(n) != n {
29 return Err(it.error("Invalid code point"));
30 }
31 match char::from_u32(n as u32) {
32 Some(c) => s.push(c),
33 None => return Err(it.error("Invalid code point")),
34 }
35 }
36 Ok(Value::str(s))
37}
38pub(crate) fn array_of(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
39 Ok(Value::Object(Obj::array(a.to_vec())))
40}
41pub(crate) fn string_raw(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
45 let strings = arg(a, 0);
46 let raw = it.get_property(&strings, "raw").unwrap_or(Value::Undefined);
47 let raw_items: Vec<Value> = match &raw {
48 Value::Object(o) => match &o.borrow().kind {
49 ObjKind::Array(items) => items.clone(),
50 _ => Vec::new(),
51 },
52 _ => Vec::new(),
53 };
54 let mut out = String::new();
55 for (i, r) in raw_items.iter().enumerate() {
56 out.push_str(&r.to_js_string());
57 if let Some(sub) = a.get(i + 1) {
58 out.push_str(&sub.to_js_string());
59 }
60 }
61 Ok(Value::str(out))
62}
63
64pub fn dom_classlist_item(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
66 if let Some(idx) = this_host_idx(&this, "classList:") {
67 let i = arg(a, 0).to_number() as usize;
68 let cls = it
69 .dom
70 .borrow()
71 .nodes
72 .get(idx)
73 .and_then(|n| n.classes.get(i).cloned());
74 return Ok(match cls {
75 Some(c) => Value::str(c),
76 None => Value::Null,
77 });
78 }
79 Ok(Value::Null)
80}
81pub fn dom_classlist_to_string(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
82 if let Some(idx) = this_host_idx(&this, "classList:") {
83 return Ok(Value::str(
84 it.dom
85 .borrow()
86 .nodes
87 .get(idx)
88 .map(|n| n.classes.join(" "))
89 .unwrap_or_default(),
90 ));
91 }
92 Ok(Value::str(""))
93}
94
95pub fn update_location(global: &Rc<RefCell<Scope>>, url: &str) {
97 let loc = match global.borrow().vars.get("location") {
98 Some(Value::Object(o)) => o.clone(),
99 _ => return,
100 };
101 let mut b = loc.borrow_mut();
102 for (k, v) in location_components(url) {
103 b.props.insert(String::from(k), Value::str(v));
104 }
105}
106
107pub(crate) fn normalize_dot_segments(path: &str) -> String {
116 let mut segs: alloc::vec::Vec<&str> = alloc::vec::Vec::new();
117 for seg in path.split('/') {
118 match seg {
119 "" | "." => {}
120 ".." => {
121 segs.pop();
122 }
123 s => segs.push(s),
124 }
125 }
126 alloc::format!("/{}", segs.join("/"))
127}
128
129pub fn resolve_url(base: &str, url: &str) -> String {
130 if url.starts_with("http://") || url.starts_with("https://") || url.starts_with("data:") {
131 return String::from(url);
132 }
133 if let Some(rest) = url.strip_prefix("//") {
141 let scheme = if base.starts_with("https://") { "https://" } else { "http://" };
142 return alloc::format!("{scheme}{rest}");
143 }
144 let (scheme, after) = if let Some(a) = base.strip_prefix("https://") {
145 ("https://", a)
146 } else if let Some(a) = base.strip_prefix("http://") {
147 ("http://", a)
148 } else {
149 return String::from(url); };
151 let (host, path) = match after.split_once('/') {
152 Some((h, p)) => (h, p), None => (after, ""),
154 };
155 if let Some(rest) = url.strip_prefix('?') {
162 let base_path = path.split('?').next().unwrap_or("").split('#').next().unwrap_or("");
163 return alloc::format!("{scheme}{host}/{base_path}?{rest}");
164 }
165 if let Some(rest) = url.strip_prefix('#') {
166 let base_path_and_query = path.split('#').next().unwrap_or("");
167 return alloc::format!("{scheme}{host}/{base_path_and_query}#{rest}");
168 }
169 let raw_path = if let Some(rooted) = url.strip_prefix('/') {
170 alloc::format!("/{rooted}")
171 } else {
172 let dir = match path.rfind('/') {
174 Some(i) => path.get(..i).unwrap_or(""),
175 None => "",
176 };
177 if dir.is_empty() {
178 alloc::format!("/{url}")
179 } else {
180 alloc::format!("/{dir}/{url}")
181 }
182 };
183 alloc::format!("{scheme}{host}{}", normalize_dot_segments(&raw_path))
184}
185
186pub(crate) fn do_http_request(
190 base: &str,
191 url: &str,
192 method: &str,
193 body: &str,
194 content_type: &str,
195) -> Result<(u16, String), &'static str> {
196 let resolved = resolve_url(base, url);
197 if resolved.starts_with("data:") {
198 return match data_url_body(&resolved) {
199 Some(b) => Ok((200, b)),
200 None => Err("malformed data URL"),
201 };
202 }
203 let (is_https, host, path) = match split_http_url(&resolved) {
204 Some(t) => t,
205 None => return Err("only absolute http(s) and data URLs are supported"),
206 };
207 let stack = crate::kernel::net_stack::TcpIpStack::new();
208 match stack.web_request(
209 is_https,
210 &host,
211 &path,
212 method,
213 content_type,
214 body.as_bytes(),
215 ) {
216 Ok(resp) => Ok((resp.status_code, resp.body)),
217 Err(_) => Err("network error"),
218 }
219}
220
221pub(crate) fn obj_prop(v: &Value, key: &str) -> Option<Value> {
223 if let Value::Object(o) = v {
224 o.borrow().props.get(key).cloned()
225 } else {
226 None
227 }
228}
229
230pub(crate) fn headers_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
237 Ok(make_headers(arg(a, 0)))
238}
239pub(crate) fn make_headers(init: Value) -> Value {
242 let o = Obj::plain();
243 if let Value::Object(io) = &init {
246 let is_headers_like = io.borrow().props.contains_key("_is_headers");
247 let is_array = matches!(&io.borrow().kind, ObjKind::Array(_));
248 if is_array {
249 if let ObjKind::Array(items) = &io.borrow().kind {
250 for pair in items {
251 if let Value::Object(p) = pair {
252 if let ObjKind::Array(kv) = &p.borrow().kind {
253 if kv.len() >= 2 {
254 let k = kv[0].to_js_string().to_lowercase();
255 o.borrow_mut().props.insert(k, Value::str(kv[1].to_js_string()));
256 }
257 }
258 }
259 }
260 }
261 } else {
262 let entries: Vec<(String, Value)> =
263 io.borrow().props.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
264 for (k, v) in entries {
265 if matches!(&v, Value::Object(f) if f.borrow().is_callable()) {
267 continue;
268 }
269 let key = k.to_lowercase();
270 o.borrow_mut().props.insert(key, Value::str(v.to_js_string()));
271 }
272 let _ = is_headers_like;
273 }
274 }
275 {
276 let mut b = o.borrow_mut();
277 b.props.insert("_is_headers".into(), Value::Bool(true));
278 b.props.insert("get".into(), nv("get", headers_get));
279 b.props.insert("set".into(), nv("set", headers_set));
280 b.props.insert("append".into(), nv("append", headers_append));
281 b.props.insert("has".into(), nv("has", headers_has));
282 b.props.insert("delete".into(), nv("delete", headers_delete));
283 b.props
284 .insert("forEach".into(), nv("forEach", headers_for_each));
285 b.props.insert("entries".into(), nv("entries", headers_entries));
288 b.props.insert("keys".into(), nv("keys", headers_keys));
289 b.props.insert("values".into(), nv("values", headers_values));
290 b.props.insert(
291 "getSetCookie".into(),
292 nv("getSetCookie", headers_get_set_cookie),
293 );
294 b.props.insert(
295 "Symbol(Symbol.iterator)".into(),
296 nv("[Symbol.iterator]", headers_entries),
297 );
298 }
299 Value::Object(o)
300}
301pub(crate) fn headers_key(a: &[Value]) -> String {
302 arg(a, 0).to_js_string().to_lowercase()
303}
304pub(crate) fn headers_get_set_cookie(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
305 if let Value::Object(o) = &this {
306 if let Some(v) = o.borrow().props.get("set-cookie") {
307 if !matches!(v, Value::Object(f) if f.borrow().is_callable()) {
308 let val_str = v.to_js_string();
309 if !val_str.is_empty() {
310 let cookies: Vec<Value> = val_str
311 .split(", ")
312 .map(|s| Value::str(s.to_string()))
313 .collect();
314 return Ok(Value::Object(Obj::array(cookies)));
315 }
316 }
317 }
318 }
319 Ok(Value::Object(Obj::array(Vec::new())))
320}
321pub(crate) fn headers_entries_sorted(this: &Value) -> Vec<(String, String)> {
325 let mut entries: Vec<(String, String)> = match this {
326 Value::Object(o) => o
327 .borrow()
328 .props
329 .iter()
330 .filter(|(k, v)| {
331 k.as_str() != "_is_headers" && !matches!(v, Value::Object(f) if f.borrow().is_callable())
332 })
333 .map(|(k, v)| (k.clone(), v.to_js_string()))
334 .collect(),
335 _ => Vec::new(),
336 };
337 entries.sort_by(|a, b| a.0.cmp(&b.0));
338 entries
339}
340pub(crate) fn headers_get(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
341 if let Value::Object(o) = &this {
342 if let Some(v) = o.borrow().props.get(&headers_key(a)) {
343 if !matches!(v, Value::Object(f) if f.borrow().is_callable()) {
344 return Ok(v.clone());
345 }
346 }
347 }
348 Ok(Value::Null)
349}
350pub(crate) fn headers_set(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
351 if let Value::Object(o) = &this {
352 o.borrow_mut()
353 .props
354 .insert(headers_key(a), Value::str(arg(a, 1).to_js_string()));
355 }
356 Ok(Value::Undefined)
357}
358pub(crate) fn headers_append(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
361 if let Value::Object(o) = &this {
362 let key = headers_key(a);
363 let v = arg(a, 1).to_js_string();
364 let existing = o.borrow().props.get(&key).map(|v| v.to_js_string());
365 let merged = match existing {
366 Some(e) if !e.is_empty() => alloc::format!("{}, {}", e, v),
367 _ => v,
368 };
369 o.borrow_mut().props.insert(key, Value::str(merged));
370 }
371 Ok(Value::Undefined)
372}
373pub(crate) fn headers_has(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
374 if let Value::Object(o) = &this {
375 let key = headers_key(a);
376 let has = matches!(o.borrow().props.get(&key), Some(v) if !matches!(v, Value::Object(f) if f.borrow().is_callable()));
377 return Ok(Value::Bool(has));
378 }
379 Ok(Value::Bool(false))
380}
381pub(crate) fn headers_delete(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
382 if let Value::Object(o) = &this {
383 o.borrow_mut().props.shift_remove(&headers_key(a));
384 }
385 Ok(Value::Undefined)
386}
387pub(crate) fn headers_for_each(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
388 let cb = arg(a, 0);
389 for (k, v) in headers_entries_sorted(&this) {
390 it.call_value(&cb, Value::Undefined, &[Value::str(v), Value::str(k), this.clone()])?;
391 }
392 Ok(Value::Undefined)
393}
394pub(crate) fn headers_entries(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
397 let items: Vec<Value> = headers_entries_sorted(&this)
398 .into_iter()
399 .map(|(k, v)| Value::Object(Obj::array(alloc::vec![Value::str(k), Value::str(v)])))
400 .collect();
401 Ok(Value::Object(make_iterator(items)))
402}
403pub(crate) fn headers_keys(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
404 let items: Vec<Value> = headers_entries_sorted(&this)
405 .into_iter()
406 .map(|(k, _)| Value::str(k))
407 .collect();
408 Ok(Value::Object(make_iterator(items)))
409}
410pub(crate) fn headers_values(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
411 let items: Vec<Value> = headers_entries_sorted(&this)
412 .into_iter()
413 .map(|(_, v)| Value::str(v))
414 .collect();
415 Ok(Value::Object(make_iterator(items)))
416}
417
418pub(crate) fn headers_content_type(headers: &Value) -> Option<String> {
420 if let Value::Object(o) = headers {
421 for (k, val) in o.borrow().props.iter() {
422 if k.eq_ignore_ascii_case("content-type") {
423 return Some(val.to_js_string());
424 }
425 }
426 }
427 None
428}
429
430pub(crate) fn body_to_string(v: &Value) -> String {
433 if let Value::Object(o) = v {
434 if let Some(q) = o.borrow().props.get("_query") {
435 return q.to_js_string();
436 }
437 }
438 v.to_js_string()
439}
440
441pub(crate) fn request_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
446 let input = arg(a, 0);
447 let opts = arg(a, 1);
448 let is_request_like = matches!(&input, Value::Object(o) if o.borrow().props.contains_key("url"));
449 let url = if is_request_like {
450 obj_prop(&input, "url").map(|v| v.to_js_string()).unwrap_or_default()
451 } else {
452 input.to_js_string()
453 };
454 let method = obj_prop(&opts, "method")
455 .or_else(|| if is_request_like { obj_prop(&input, "method") } else { None })
456 .map(|v| v.to_js_string())
457 .unwrap_or_else(|| String::from("GET"));
458 let body = obj_prop(&opts, "body")
459 .or_else(|| if is_request_like { obj_prop(&input, "_body") } else { None })
460 .map(|v| v.to_js_string())
461 .unwrap_or_default();
462 let headers_raw = obj_prop(&opts, "headers")
467 .or_else(|| if is_request_like { obj_prop(&input, "headers") } else { None })
468 .unwrap_or(Value::Undefined);
469 let headers = make_headers(headers_raw);
470 let signal = obj_prop(&opts, "signal")
471 .or_else(|| if is_request_like { obj_prop(&input, "signal") } else { None })
472 .unwrap_or(Value::Undefined);
473 let req = Obj::plain();
474 {
475 let mut r = req.borrow_mut();
476 r.props.insert("url".into(), Value::str(url));
477 r.props.insert("method".into(), Value::str(method));
478 r.props.insert("_body".into(), Value::str(body));
479 r.props.insert("headers".into(), headers);
480 r.props.insert("signal".into(), signal);
481 }
482 Ok(Value::Object(req))
483}
484
485pub(crate) fn navigator_send_beacon(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
492 let url = arg(a, 0);
493 let data = arg(a, 1);
494 let opts = Obj::plain();
495 opts.borrow_mut().props.insert("method".into(), Value::str("POST"));
496 if !matches!(data, Value::Undefined) {
497 opts.borrow_mut().props.insert("body".into(), data);
498 }
499 let _ = fetch(it, Value::Undefined, &[url, Value::Object(opts)]);
500 Ok(Value::Bool(true))
501}
502
503pub(crate) fn make_geolocation_error() -> Value {
507 let o = Obj::plain();
508 let mut b = o.borrow_mut();
509 b.props.insert("code".into(), Value::Number(1.0));
510 b.props.insert(
511 "message".into(),
512 Value::str("Geolocation is not supported in this environment"),
513 );
514 b.props.insert("PERMISSION_DENIED".into(), Value::Number(1.0));
515 b.props.insert("POSITION_UNAVAILABLE".into(), Value::Number(2.0));
516 b.props.insert("TIMEOUT".into(), Value::Number(3.0));
517 drop(b);
518 Value::Object(o)
519}
520pub(crate) fn geolocation_get_current_position(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
525 if let Some(err_cb) = callable_or_none(arg(a, 1)) {
526 it.call_listener(
527 &err_cb,
528 Value::Undefined,
529 &[make_geolocation_error()],
530 "geolocation error callback",
531 );
532 }
533 Ok(Value::Undefined)
534}
535pub(crate) fn geolocation_watch_position(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
539 geolocation_get_current_position(it, this, a)?;
540 Ok(Value::Number(TIMER_ID.fetch_add(1, Ordering::Relaxed) as f64))
541}
542pub(crate) fn geolocation_clear_watch(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
544 Ok(Value::Undefined)
545}
546pub(crate) fn permissions_query(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
550 let name = obj_prop(&arg(a, 0), "name").map(|v| v.to_js_string()).unwrap_or_default();
551 let state = match name.as_str() {
552 "geolocation" | "camera" | "microphone" => "denied",
553 "notifications" => "granted",
554 _ => "prompt",
555 };
556 let status = Obj::plain();
557 {
558 let mut b = status.borrow_mut();
559 b.props.insert("state".into(), Value::str(state));
560 b.props.insert("name".into(), Value::str(name));
561 b.props.insert("onchange".into(), Value::Null);
562 }
563 Ok(resolved_promise(it, Value::Object(status)))
564}
565
566pub(crate) fn wake_lock_request(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
570 let ty = arg(a, 0).to_js_string();
571 let ty = if ty.is_empty() { String::from("screen") } else { ty };
572 let sentinel = Obj::plain();
573 {
574 let mut b = sentinel.borrow_mut();
575 b.props.insert("released".into(), Value::Bool(false));
576 b.props.insert("type".into(), Value::str(ty));
577 b.props
578 .insert("release".into(), nv("WakeLockSentinel.release", wake_lock_sentinel_release));
579 }
580 Ok(resolved_promise(it, Value::Object(sentinel)))
581}
582pub(crate) fn wake_lock_sentinel_release(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
584 if let Value::Object(o) = &this {
585 o.borrow_mut().props.insert("released".into(), Value::Bool(true));
586 }
587 Ok(resolved_promise(it, Value::Undefined))
588}
589
590pub(crate) fn navigator_can_share(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
594 Ok(Value::Bool(false))
595}
596pub(crate) fn navigator_share(it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
600 let err = Obj::plain();
601 {
602 let mut b = err.borrow_mut();
603 b.props.insert("name".into(), Value::str("AbortError"));
604 b.props.insert(
605 "message".into(),
606 Value::str("Share was cancelled: no share target available in this environment"),
607 );
608 }
609 Ok(rejected_promise(it, Value::Object(err)))
610}
611
612pub(crate) fn eye_dropper_ctor(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
615 let o = Obj::plain();
616 o.borrow_mut().props.insert("open".into(), nv("EyeDropper.open", eye_dropper_open));
617 Ok(Value::Object(o))
618}
619pub(crate) fn eye_dropper_open(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
623 let err = Obj::plain();
624 {
625 let mut b = err.borrow_mut();
626 b.props.insert("name".into(), Value::str("AbortError"));
627 b.props.insert(
628 "message".into(),
629 Value::str("The color picker was cancelled: no picker UI available in this environment"),
630 );
631 }
632 Ok(rejected_promise(it, Value::Object(err)))
633}
634
635pub(crate) fn navigator_vibrate(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
639 Ok(Value::Bool(true))
640}
641
642pub(crate) fn navigator_locks_request(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
651 let cb = if matches!(arg(a, 2), Value::Object(o) if o.borrow().is_callable()) {
652 arg(a, 2)
653 } else {
654 arg(a, 1)
655 };
656 let name = arg(a, 0).to_js_string();
657 let lock = Obj::plain();
658 {
659 let mut b = lock.borrow_mut();
660 b.props.insert("name".into(), Value::str(name));
661 b.props.insert("mode".into(), Value::str("exclusive"));
662 }
663 let result = it.call_value(&cb, Value::Undefined, &[Value::Object(lock)])?;
664 Ok(resolved_promise(it, result))
665}
666pub(crate) fn navigator_locks_query(it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
669 let o = Obj::plain();
670 {
671 let mut b = o.borrow_mut();
672 b.props.insert("held".into(), Value::Object(Obj::array(alloc::vec![])));
673 b.props.insert("pending".into(), Value::Object(Obj::array(alloc::vec![])));
674 }
675 Ok(resolved_promise(it, Value::Object(o)))
676}
677pub(crate) fn navigator_get_battery(it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
678 let battery = Obj::plain();
679 {
680 let mut b = battery.borrow_mut();
681 b.props.insert("charging".into(), Value::Bool(true));
682 b.props.insert("chargingTime".into(), Value::Number(0.0));
683 b.props.insert("dischargingTime".into(), Value::Number(f64::INFINITY));
684 b.props.insert("level".into(), Value::Number(1.0));
685 b.props
686 .insert("addEventListener".into(), nv("addEventListener", dom_noop));
687 b.props
688 .insert("removeEventListener".into(), nv("removeEventListener", dom_noop));
689 }
690 Ok(resolved_promise(it, Value::Object(battery)))
691}
692
693pub(crate) fn fetch(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
696 let first = arg(a, 0);
697 let is_request_like = matches!(&first, Value::Object(o) if o.borrow().props.contains_key("url"));
701 let url = if is_request_like {
702 obj_prop(&first, "url").map(|v| v.to_js_string()).unwrap_or_default()
703 } else {
704 first.to_js_string()
705 };
706 let opts = arg(a, 1);
707 let method = obj_prop(&opts, "method")
708 .or_else(|| if is_request_like { obj_prop(&first, "method") } else { None })
709 .map(|v| v.to_js_string())
710 .unwrap_or_else(|| String::from("GET"));
711 let body = obj_prop(&opts, "body")
712 .or_else(|| if is_request_like { obj_prop(&first, "_body") } else { None })
713 .map(|v| body_to_string(&v))
714 .unwrap_or_default();
715 let headers = obj_prop(&opts, "headers")
716 .or_else(|| if is_request_like { obj_prop(&first, "headers") } else { None })
717 .unwrap_or(Value::Undefined);
718 let content_type =
719 headers_content_type(&headers).unwrap_or_else(|| String::from("text/plain;charset=UTF-8"));
720 let signal_opt = obj_prop(&opts, "signal")
727 .or_else(|| if is_request_like { obj_prop(&first, "signal") } else { None });
728 if let Some(signal) = signal_opt {
729 let already_aborted = matches!(&signal, Value::Object(o) if matches!(o.borrow().props.get("aborted"), Some(Value::Bool(true))));
730 if already_aborted {
731 let reason = match &signal {
732 Value::Object(o) => o.borrow().props.get("reason").cloned().unwrap_or(Value::Undefined),
733 _ => Value::Undefined,
734 };
735 return Ok(rejected_promise(it, reason));
736 }
737 }
738 let base = it.base_url.clone();
739 let resolved_url = resolve_url(&base, &url);
740 match do_http_request(&base, &url, &method, &body, &content_type) {
741 Ok((status, body)) => {
742 Ok(resolved_promise(it, make_response_with_url(status, body, resolved_url)))
743 }
744 Err(msg) => Ok(rejected_promise(it, it.error(format!("fetch: {}", msg)))),
745 }
746}
747
748pub(crate) fn xhr_ctor(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
751 let o = Obj::plain();
752 {
753 let mut b = o.borrow_mut();
754 b.props.insert("readyState".into(), Value::Number(0.0));
755 b.props.insert("status".into(), Value::Number(0.0));
756 b.props.insert("statusText".into(), Value::str(""));
757 b.props.insert("responseText".into(), Value::str(""));
758 b.props.insert("response".into(), Value::str(""));
759 b.props.insert("open".into(), nv("open", xhr_open));
760 b.props.insert("send".into(), nv("send", xhr_send));
761 b.props.insert(
762 "setRequestHeader".into(),
763 nv("setRequestHeader", xhr_set_request_header),
764 );
765 b.props.insert("abort".into(), nv("abort", xhr_noop));
766 b.props.insert(
767 "getAllResponseHeaders".into(),
768 nv("getAllResponseHeaders", xhr_empty_str),
769 );
770 b.props.insert(
775 "getResponseHeader".into(),
776 nv("getResponseHeader", xhr_get_response_header),
777 );
778 b.props.insert(
779 "addEventListener".into(),
780 nv("addEventListener", xhr_add_event_listener),
781 );
782 b.props.insert(
786 "removeEventListener".into(),
787 nv("removeEventListener", xhr_remove_event_listener),
788 );
789 }
790 Ok(Value::Object(o))
791}
792
793pub(crate) fn xhr_noop(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
794 Ok(Value::Undefined)
795}
796
797pub(crate) fn xhr_set_request_header(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
799 if let Value::Object(o) = &this {
800 if arg(a, 0)
801 .to_js_string()
802 .eq_ignore_ascii_case("content-type")
803 {
804 o.borrow_mut()
805 .props
806 .insert("_content_type".into(), Value::str(arg(a, 1).to_js_string()));
807 }
808 }
809 Ok(Value::Undefined)
810}
811
812pub(crate) fn xhr_empty_str(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
813 Ok(Value::str(""))
814}
815pub(crate) fn xhr_get_response_header(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
816 Ok(Value::Null)
817}
818
819pub(crate) fn xhr_add_event_listener(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
821 if let Value::Object(o) = &this {
822 o.borrow_mut()
823 .props
824 .insert(format!("on{}", arg(a, 0).to_js_string()), arg(a, 1));
825 }
826 Ok(Value::Undefined)
827}
828pub(crate) fn xhr_remove_event_listener(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
832 if let Value::Object(o) = &this {
833 let key = format!("on{}", arg(a, 0).to_js_string());
834 let target = arg(a, 1);
835 let matches = match (o.borrow().props.get(&key), &target) {
836 (Some(Value::Object(stored)), Value::Object(t)) => Rc::ptr_eq(stored, t),
837 _ => false,
838 };
839 if matches {
840 o.borrow_mut().props.shift_remove(&key);
841 }
842 }
843 Ok(Value::Undefined)
844}
845
846pub(crate) fn xhr_open(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
847 if let Value::Object(o) = &this {
848 let mut b = o.borrow_mut();
849 b.props
850 .insert("_method".into(), Value::str(arg(a, 0).to_js_string()));
851 b.props
852 .insert("_url".into(), Value::str(arg(a, 1).to_js_string()));
853 b.props.insert("readyState".into(), Value::Number(1.0));
854 }
855 Ok(Value::Undefined)
856}
857
858pub(crate) fn xhr_send(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
859 let o = match &this {
860 Value::Object(o) => o.clone(),
861 _ => return Ok(Value::Undefined),
862 };
863 let (url, method, content_type) = {
864 let b = o.borrow();
865 (
866 b.props
867 .get("_url")
868 .map(|v| v.to_js_string())
869 .unwrap_or_default(),
870 b.props
871 .get("_method")
872 .map(|v| v.to_js_string())
873 .unwrap_or_else(|| String::from("GET")),
874 b.props
875 .get("_content_type")
876 .map(|v| v.to_js_string())
877 .unwrap_or_else(|| String::from("text/plain;charset=UTF-8")),
878 )
879 };
880 let req_body = match arg(a, 0) {
882 Value::Undefined | Value::Null => String::new(),
883 other => other.to_js_string(),
884 };
885 let base = it.base_url.clone();
886 let (status, body, ok) = match do_http_request(&base, &url, &method, &req_body, &content_type) {
887 Ok((s, b)) => (s, b, true),
888 Err(e) => {
889 crate::warn!("[XHR] 取得失敗 url={:?} 理由={}", url, e);
892 (0u16, String::new(), false)
893 }
894 };
895 {
896 let mut b = o.borrow_mut();
897 b.props
898 .insert("status".into(), Value::Number(status as f64));
899 b.props
903 .insert("statusText".into(), Value::str(http_status_text(status)));
904 b.props
905 .insert("responseText".into(), Value::str(body.clone()));
906 b.props.insert("response".into(), Value::str(body));
907 b.props.insert("readyState".into(), Value::Number(4.0));
908 }
909 let onrsc = o.borrow().props.get("onreadystatechange").cloned();
910 if let Some(f) = onrsc.and_then(callable_or_none) {
911 it.call_listener(&f, this.clone(), &[], "XHR onreadystatechange");
912 }
913 let cb = o
914 .borrow()
915 .props
916 .get(if ok { "onload" } else { "onerror" })
917 .cloned();
918 if let Some(f) = cb.and_then(callable_or_none) {
919 let ctx = if ok { "XHR onload" } else { "XHR onerror" };
920 it.call_listener(&f, this.clone(), &[], ctx);
921 }
922 Ok(Value::Undefined)
923}
924
925pub(crate) fn promise_all_static(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
927 let items = it.iter_to_vec(&arg(a, 0));
928 let result = new_pending();
929 it.run_microtasks();
931 let mut out: Vec<Value> = Vec::new();
932 let mut rejected: Option<Value> = None;
933 for item in items {
934 match resolve_maybe_thenable(it, &item) {
939 Some(s) => {
940 let b = s.borrow();
941 match b.status {
942 PromiseStatus::Fulfilled => out.push(b.value.clone()),
943 PromiseStatus::Rejected => {
944 rejected = Some(b.value.clone());
945 break;
946 }
947 PromiseStatus::Pending => out.push(Value::Undefined),
948 }
949 }
950 None => out.push(item), }
952 }
953 match rejected {
954 Some(e) => it.promise_reject(&result, e),
955 None => it.promise_resolve(&result, Value::Object(Obj::array(out))),
956 }
957 Ok(Value::Object(Obj::promise(result)))
958}
959
960pub fn promise_method(key: &str) -> Value {
962 match key {
963 "then" => nv("Promise.then", promise_then_m),
964 "catch" => nv("Promise.catch", promise_catch_m),
965 "finally" => nv("Promise.finally", promise_finally_m),
966 _ => Value::Undefined,
967 }
968}
969
970pub(crate) fn promise_then_m(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
971 if let Some(s) = this_promise_state(&this) {
972 let on_f = callable_or_none(arg(a, 0));
973 let on_r = callable_or_none(arg(a, 1));
974 return Ok(it.promise_then(&s, on_f, on_r));
975 }
976 Ok(Value::Undefined)
977}
978
979pub(crate) fn promise_catch_m(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
980 if let Some(s) = this_promise_state(&this) {
981 let on_r = callable_or_none(arg(a, 0));
982 return Ok(it.promise_then(&s, None, on_r));
983 }
984 Ok(Value::Undefined)
985}
986
987pub(crate) fn promise_finally_m(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
996 let Some(s) = this_promise_state(&this) else {
997 return Ok(Value::Undefined);
998 };
999 let cb = arg(a, 0);
1000 if callable_or_none(cb.clone()).is_none() {
1001 return Ok(it.promise_then(&s, None, None));
1003 }
1004 it.global
1005 .borrow_mut()
1006 .vars
1007 .insert("__finally_promise".into(), this);
1008 it.global.borrow_mut().vars.insert("__finally_cb".into(), cb);
1009 let src = "(function(){ \
1010 var p = __finally_promise; \
1011 var cb = __finally_cb; \
1012 return p.then( \
1013 function(v){ return Promise.resolve(cb()).then(function(){ return v; }); }, \
1014 function(r){ return Promise.resolve(cb()).then(function(){ throw r; }); } \
1015 ); \
1016 })()";
1017 let result = it.eval_source(src);
1018 it.global.borrow_mut().vars.remove("__finally_promise");
1019 it.global.borrow_mut().vars.remove("__finally_cb");
1020 result
1021}
1022