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((scheme, _)) = url.split_once(':') {
138 let mut cs = scheme.chars();
139 if cs.next().is_some_and(|c| c.is_ascii_alphabetic())
140 && cs.all(|c| c.is_ascii_alphanumeric() || matches!(c, '+' | '-' | '.'))
141 && !scheme.contains('/')
142 {
143 return String::from(url);
144 }
145 }
146 if let Some(rest) = url.strip_prefix("//") {
154 let scheme = if base.starts_with("https://") { "https://" } else { "http://" };
155 return alloc::format!("{scheme}{rest}");
156 }
157 let (scheme, after) = if let Some(a) = base.strip_prefix("https://") {
158 ("https://", a)
159 } else if let Some(a) = base.strip_prefix("http://") {
160 ("http://", a)
161 } else {
162 return String::from(url); };
164 let (host, path) = match after.split_once('/') {
165 Some((h, p)) => (h, p), None => (after, ""),
167 };
168 if let Some(rest) = url.strip_prefix('?') {
175 let base_path = path.split('?').next().unwrap_or("").split('#').next().unwrap_or("");
176 return alloc::format!("{scheme}{host}/{base_path}?{rest}");
177 }
178 if let Some(rest) = url.strip_prefix('#') {
179 let base_path_and_query = path.split('#').next().unwrap_or("");
180 return alloc::format!("{scheme}{host}/{base_path_and_query}#{rest}");
181 }
182 let raw_path = if let Some(rooted) = url.strip_prefix('/') {
183 alloc::format!("/{rooted}")
184 } else {
185 let dir = match path.rfind('/') {
187 Some(i) => path.get(..i).unwrap_or(""),
188 None => "",
189 };
190 if dir.is_empty() {
191 alloc::format!("/{url}")
192 } else {
193 alloc::format!("/{dir}/{url}")
194 }
195 };
196 alloc::format!("{scheme}{host}{}", normalize_dot_segments(&raw_path))
197}
198
199pub(crate) fn do_http_request(
203 base: &str,
204 url: &str,
205 method: &str,
206 body: &str,
207 content_type: &str,
208) -> Result<(u16, String), &'static str> {
209 do_http_request_raw(base, url, method, body.as_bytes(), content_type, &[]).map(|(s, b, _)| (s, b))
210}
211
212pub(crate) fn body_to_bytes(v: &Value) -> Vec<u8> {
217 match v {
218 Value::Undefined | Value::Null => Vec::new(),
219 Value::Str(s) => s.as_bytes().to_vec(),
220 Value::Object(o) => {
221 let (is_binary, ta_bytes) = {
222 let b = o.borrow();
223 let ta = b.props.get("BYTES_PER_ELEMENT").map(|v| v.to_number() as usize).filter(|_| b.props.contains_key("_ta_kind"));
224 let kind = b.props.get("_ta_kind").map(|v| v.to_js_string()).unwrap_or_default();
225 let binary = ta.is_some()
226 || b.props.contains_key("_blob_bytes")
227 || b.props.contains_key("_bytes")
228 || b.props.contains_key("_dv_bytes");
229 (binary, ta.map(|n| (n, kind)))
230 };
231 if !is_binary {
232 if o.borrow().props.contains_key("_query") {
233 return body_to_string(v).into_bytes();
234 }
235 return v.to_js_string().into_bytes();
236 }
237 if let Some((n, kind)) = ta_bytes {
238 if n > 1 {
239 let mut out = Vec::new();
241 if let ObjKind::Array(items) = &o.borrow().kind {
242 for x in items {
243 let f = x.to_number();
244 match (kind.as_str(), n) {
245 ("Float32Array", _) => out.extend_from_slice(&(f as f32).to_le_bytes()),
246 ("Float64Array", _) => out.extend_from_slice(&f.to_le_bytes()),
247 (_, 2) => out.extend_from_slice(&(f as i64 as u16).to_le_bytes()),
248 (_, 4) => out.extend_from_slice(&(f as i64 as u32).to_le_bytes()),
249 _ => out.extend_from_slice(&(f as i64 as u64).to_le_bytes()),
250 }
251 }
252 }
253 return out;
254 }
255 }
256 let mut f = Vec::new();
257 super::typed_array::blob_part_bytes(v, &mut f);
258 f.into_iter().map(|x| x as i64 as u8).collect()
259 }
260 other => other.to_js_string().into_bytes(),
261 }
262}
263
264fn request_header_allowed(name: &str, value: &str) -> bool {
266 const FORBIDDEN: &[&str] = &[
267 "host", "content-length", "connection", "accept-encoding", "accept-charset", "cookie", "cookie2",
268 "date", "dnt", "expect", "keep-alive", "origin", "referer", "te", "trailer", "transfer-encoding",
269 "upgrade", "via", "content-type", "user-agent",
270 ];
271 let n = name.trim();
272 !n.is_empty()
273 && n.bytes().all(|c| c.is_ascii_graphic() && c != b':')
274 && !value.bytes().any(|c| c == b'\r' || c == b'\n')
275 && !FORBIDDEN.iter().any(|f| n.eq_ignore_ascii_case(f))
276 && !n.to_ascii_lowercase().starts_with("proxy-")
277 && !n.to_ascii_lowercase().starts_with("sec-")
278}
279
280pub(crate) fn do_http_request_raw(
282 base: &str,
283 url: &str,
284 method: &str,
285 body: &[u8],
286 content_type: &str,
287 headers: &[(String, String)],
288) -> Result<(u16, String, Vec<u8>), &'static str> {
289 do_http_request_full(base, url, method, body, content_type, headers).map(|(s, b, raw, _)| (s, b, raw))
290}
291
292pub(crate) fn do_http_request_full(
295 base: &str,
296 url: &str,
297 method: &str,
298 body: &[u8],
299 content_type: &str,
300 headers: &[(String, String)],
301) -> Result<(u16, String, Vec<u8>, alloc::collections::BTreeMap<String, String>), &'static str> {
302 let resolved = resolve_url(base, url);
303 if resolved.starts_with("data:") {
304 return match data_url_body(&resolved) {
305 Some(b) => {
306 let bytes = b.as_bytes().to_vec();
307 Ok((200, b, bytes, alloc::collections::BTreeMap::new()))
308 }
309 None => Err("malformed data URL"),
310 };
311 }
312 let (is_https, host, path) = match split_http_url(&resolved) {
313 Some(t) => t,
314 None => return Err("only absolute http(s) and data URLs are supported"),
315 };
316 let stack = crate::kernel::net_stack::TcpIpStack::new();
317 let mut hdrs: Vec<(String, String)> = headers
319 .iter()
320 .filter(|(k, v)| request_header_allowed(k, v))
321 .cloned()
322 .collect();
323 if let Some((b_https, b_host, b_path)) = split_http_url(base) {
324 let m = method.to_ascii_uppercase();
325 if m != "GET" && m != "HEAD" {
326 hdrs.push((String::from("Origin"), format!("{}://{}", if b_https { "https" } else { "http" }, b_host)));
327 }
328 if let Some(r) = crate::os_lib::web_engine::referrer::referrer_for(b_https, &b_host, &b_path, is_https, &host) {
329 hdrs.push((String::from("Referer"), r));
330 }
331 }
332 let hdr_refs: Vec<(&str, &str)> = hdrs.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
333 match stack.web_request(
334 is_https,
335 &host,
336 &path,
337 method,
338 content_type,
339 body,
340 &hdr_refs,
341 ) {
342 Ok(resp) => {
343 if resp.status_code >= 400 {
344 crate::warn!(
345 "[XHR] HTTP {} {} {}: {}",
346 resp.status_code,
347 method,
348 resolved.chars().take(120).collect::<String>(),
349 String::from_utf8_lossy(&resp.body_bytes).chars().take(200).collect::<String>().replace('\n', " ")
350 );
351 }
352 let declared = resp
355 .headers
356 .iter()
357 .find(|(k, _)| k.eq_ignore_ascii_case("content-length"))
358 .and_then(|(_, v)| v.trim().parse::<usize>().ok());
359 if let Some(len) = declared {
360 if resp.body.len() < len {
361 crate::warn!("[XHR] 途中切れ url={} 受信={} Content-Length={}", resolved, resp.body.len(), len);
362 }
363 }
364 Ok((resp.status_code, resp.body, resp.body_bytes, resp.headers))
365 }
366 Err(_) => Err("network error"),
367 }
368}
369
370pub(crate) fn obj_prop(v: &Value, key: &str) -> Option<Value> {
372 if let Value::Object(o) = v {
373 o.borrow().props.get(key).cloned()
374 } else {
375 None
376 }
377}
378
379pub(crate) fn headers_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
386 Ok(make_headers(arg(a, 0)))
387}
388pub(crate) fn make_headers(init: Value) -> Value {
391 let o = Obj::plain();
392 if let Value::Object(io) = &init {
395 let is_headers_like = io.borrow().props.contains_key("_is_headers");
396 let is_array = matches!(&io.borrow().kind, ObjKind::Array(_));
397 if is_array {
398 if let ObjKind::Array(items) = &io.borrow().kind {
399 for pair in items {
400 if let Value::Object(p) = pair {
401 if let ObjKind::Array(kv) = &p.borrow().kind {
402 if kv.len() >= 2 {
403 let k = kv[0].to_js_string().to_lowercase();
404 o.borrow_mut().props.insert(k, Value::str(kv[1].to_js_string()));
405 }
406 }
407 }
408 }
409 }
410 } else {
411 let entries: Vec<(String, Value)> =
412 io.borrow().props.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
413 for (k, v) in entries {
414 if matches!(&v, Value::Object(f) if f.borrow().is_callable()) {
416 continue;
417 }
418 let key = k.to_lowercase();
419 o.borrow_mut().props.insert(key, Value::str(v.to_js_string()));
420 }
421 let _ = is_headers_like;
422 }
423 }
424 {
425 let mut b = o.borrow_mut();
426 b.props.insert("_is_headers".into(), Value::Bool(true));
427 b.props.insert("get".into(), nv("get", headers_get));
428 b.props.insert("set".into(), nv("set", headers_set));
429 b.props.insert("append".into(), nv("append", headers_append));
430 b.props.insert("has".into(), nv("has", headers_has));
431 b.props.insert("delete".into(), nv("delete", headers_delete));
432 b.props
433 .insert("forEach".into(), nv("forEach", headers_for_each));
434 b.props.insert("entries".into(), nv("entries", headers_entries));
437 b.props.insert("keys".into(), nv("keys", headers_keys));
438 b.props.insert("values".into(), nv("values", headers_values));
439 b.props.insert(
440 "getSetCookie".into(),
441 nv("getSetCookie", headers_get_set_cookie),
442 );
443 b.props.insert(
444 "Symbol(Symbol.iterator)".into(),
445 nv("[Symbol.iterator]", headers_entries),
446 );
447 }
448 Value::Object(o)
449}
450pub(crate) fn headers_key(a: &[Value]) -> String {
451 arg(a, 0).to_js_string().to_lowercase()
452}
453pub(crate) fn headers_get_set_cookie(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
454 if let Value::Object(o) = &this {
455 if let Some(v) = o.borrow().props.get("set-cookie") {
456 if !matches!(v, Value::Object(f) if f.borrow().is_callable()) {
457 let val_str = v.to_js_string();
458 if !val_str.is_empty() {
459 let cookies: Vec<Value> = val_str
460 .split(", ")
461 .map(|s| Value::str(s.to_string()))
462 .collect();
463 return Ok(Value::Object(Obj::array(cookies)));
464 }
465 }
466 }
467 }
468 Ok(Value::Object(Obj::array(Vec::new())))
469}
470pub(crate) fn headers_entries_sorted(this: &Value) -> Vec<(String, String)> {
474 let mut entries: Vec<(String, String)> = match this {
475 Value::Object(o) => o
476 .borrow()
477 .props
478 .iter()
479 .filter(|(k, v)| {
480 k.as_str() != "_is_headers" && !matches!(v, Value::Object(f) if f.borrow().is_callable())
481 })
482 .map(|(k, v)| (k.clone(), v.to_js_string()))
483 .collect(),
484 _ => Vec::new(),
485 };
486 entries.sort_by(|a, b| a.0.cmp(&b.0));
487 entries
488}
489pub(crate) fn headers_get(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
490 if let Value::Object(o) = &this {
491 if let Some(v) = o.borrow().props.get(&headers_key(a)) {
492 if !matches!(v, Value::Object(f) if f.borrow().is_callable()) {
493 return Ok(v.clone());
494 }
495 }
496 }
497 Ok(Value::Null)
498}
499pub(crate) fn headers_set(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
500 if let Value::Object(o) = &this {
501 o.borrow_mut()
502 .props
503 .insert(headers_key(a), Value::str(arg(a, 1).to_js_string()));
504 }
505 Ok(Value::Undefined)
506}
507pub(crate) fn headers_append(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
510 if let Value::Object(o) = &this {
511 let key = headers_key(a);
512 let v = arg(a, 1).to_js_string();
513 let existing = o.borrow().props.get(&key).map(|v| v.to_js_string());
514 let merged = match existing {
515 Some(e) if !e.is_empty() => alloc::format!("{}, {}", e, v),
516 _ => v,
517 };
518 o.borrow_mut().props.insert(key, Value::str(merged));
519 }
520 Ok(Value::Undefined)
521}
522pub(crate) fn headers_has(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
523 if let Value::Object(o) = &this {
524 let key = headers_key(a);
525 let has = matches!(o.borrow().props.get(&key), Some(v) if !matches!(v, Value::Object(f) if f.borrow().is_callable()));
526 return Ok(Value::Bool(has));
527 }
528 Ok(Value::Bool(false))
529}
530pub(crate) fn headers_delete(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
531 if let Value::Object(o) = &this {
532 o.borrow_mut().props.shift_remove(&headers_key(a));
533 }
534 Ok(Value::Undefined)
535}
536pub(crate) fn headers_for_each(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
537 let cb = arg(a, 0);
538 for (k, v) in headers_entries_sorted(&this) {
539 it.call_value(&cb, Value::Undefined, &[Value::str(v), Value::str(k), this.clone()])?;
540 }
541 Ok(Value::Undefined)
542}
543pub(crate) fn headers_entries(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
546 let items: Vec<Value> = headers_entries_sorted(&this)
547 .into_iter()
548 .map(|(k, v)| Value::Object(Obj::array(alloc::vec![Value::str(k), Value::str(v)])))
549 .collect();
550 Ok(Value::Object(make_iterator(items)))
551}
552pub(crate) fn headers_keys(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
553 let items: Vec<Value> = headers_entries_sorted(&this)
554 .into_iter()
555 .map(|(k, _)| Value::str(k))
556 .collect();
557 Ok(Value::Object(make_iterator(items)))
558}
559pub(crate) fn headers_values(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
560 let items: Vec<Value> = headers_entries_sorted(&this)
561 .into_iter()
562 .map(|(_, v)| Value::str(v))
563 .collect();
564 Ok(Value::Object(make_iterator(items)))
565}
566
567pub(crate) fn headers_list(headers: &Value) -> Vec<(String, String)> {
570 let mut out = Vec::new();
571 if let Value::Object(o) = headers {
572 for (k, val) in o.borrow().props.iter() {
573 if k.starts_with('_') || matches!(val, Value::Object(f) if f.borrow().is_callable()) {
574 continue;
575 }
576 out.push((k.clone(), val.to_js_string()));
577 }
578 }
579 out
580}
581
582pub(crate) fn headers_content_type(headers: &Value) -> Option<String> {
583 if let Value::Object(o) = headers {
584 for (k, val) in o.borrow().props.iter() {
585 if k.eq_ignore_ascii_case("content-type") {
586 return Some(val.to_js_string());
587 }
588 }
589 }
590 None
591}
592
593pub(crate) fn body_to_string(v: &Value) -> String {
596 if let Value::Object(o) = v {
597 let b = o.borrow();
598 if let Some(q) = b.props.get("_query") {
599 return q.to_js_string();
600 }
601 if let Some(Value::Object(arr)) = b.props.get("_blob_bytes") {
604 if let ObjKind::Array(items) = &arr.borrow().kind {
605 let bytes: alloc::vec::Vec<u8> = items
606 .iter()
607 .map(|x| match x {
608 Value::Number(n) => *n as u8,
609 _ => 0,
610 })
611 .collect();
612 return String::from_utf8_lossy(&bytes).into_owned();
613 }
614 }
615 }
616 v.to_js_string()
617}
618
619pub(crate) fn request_ctor(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
624 let input = arg(a, 0);
625 let opts = arg(a, 1);
626 let is_request_like = matches!(&input, Value::Object(o) if o.borrow().props.contains_key("url"));
627 let url = if is_request_like {
628 let u = obj_prop(&input, "url").unwrap_or(Value::Undefined);
629 it.to_string_value(u)?
630 } else {
631 it.to_string_value(input.clone())?
633 };
634 let method = obj_prop(&opts, "method")
635 .or_else(|| if is_request_like { obj_prop(&input, "method") } else { None })
636 .map(|v| v.to_js_string())
637 .unwrap_or_else(|| String::from("GET"));
638 let body = obj_prop(&opts, "body")
641 .or_else(|| if is_request_like { obj_prop(&input, "_body") } else { None })
642 .map(|v| match v {
643 Value::Object(_) | Value::Str(_) => v,
644 Value::Undefined | Value::Null => Value::str(""),
645 other => Value::str(other.to_js_string()),
646 })
647 .unwrap_or_else(|| Value::str(""));
648 let headers_raw = obj_prop(&opts, "headers")
653 .or_else(|| if is_request_like { obj_prop(&input, "headers") } else { None })
654 .unwrap_or(Value::Undefined);
655 let headers = make_headers(headers_raw);
656 let signal = obj_prop(&opts, "signal")
657 .or_else(|| if is_request_like { obj_prop(&input, "signal") } else { None })
658 .unwrap_or(Value::Undefined);
659 let req = Obj::plain();
660 {
661 let mut r = req.borrow_mut();
662 r.props.insert("url".into(), Value::str(url.clone()));
663 r.props.insert("method".into(), Value::str(method.clone()));
664 r.props.insert("_req_url".into(), Value::str(url));
668 r.props.insert("_req_method".into(), Value::str(method));
669 r.props.insert("_body".into(), body);
670 r.props.insert("headers".into(), headers);
671 r.props.insert("signal".into(), signal);
672 }
673 Ok(Value::Object(req))
674}
675
676pub(crate) fn navigator_send_beacon(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
683 let url = arg(a, 0).to_js_string();
684 let data = arg(a, 1);
685 let resolved = resolve_url(&it.base_url, &url);
691 if resolved.starts_with("data:") {
692 return Ok(Value::Bool(true));
693 }
694 let body = if matches!(data, Value::Undefined) { String::new() } else { body_to_string(&data) };
695 let content_type = match &data {
696 Value::Object(o) => o
697 .borrow()
698 .props
699 .get("type")
700 .map(|v| v.to_js_string())
701 .filter(|t| !t.is_empty())
702 .unwrap_or_else(|| String::from("text/plain;charset=UTF-8")),
703 _ => String::from("text/plain;charset=UTF-8"),
704 };
705 BEACON_QUEUE.lock().push_back((resolved, body, content_type));
706 if !BEACON_WORKER.swap(true, core::sync::atomic::Ordering::AcqRel) {
707 let pid = crate::kernel::scheduler::spawn(
708 beacon_thread,
709 crate::kernel::scheduler::Priority::Low,
710 "beacon",
711 );
712 if pid == 0 {
713 BEACON_WORKER.store(false, core::sync::atomic::Ordering::Release);
715 }
716 }
717 Ok(Value::Bool(true))
718}
719
720static BEACON_QUEUE: spin::Mutex<alloc::collections::VecDeque<(String, String, String)>> =
722 spin::Mutex::new(alloc::collections::VecDeque::new());
723static BEACON_WORKER: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
725
726fn beacon_thread() {
728 loop {
729 let job = BEACON_QUEUE.lock().pop_front();
730 match job {
731 Some((url, body, content_type)) => {
732 match do_http_request("", &url, "POST", &body, &content_type) {
733 Ok((status, _)) => crate::debug!("[BEACON] sent status={} url={}", status, url),
734 Err(e) => crate::warn!("[BEACON] 送信失敗 url={} err={}", url, e),
735 }
736 crate::kernel::scheduler::yield_now();
737 }
738 None => {
739 BEACON_WORKER.store(false, core::sync::atomic::Ordering::Release);
740 if BEACON_QUEUE.lock().is_empty()
742 || BEACON_WORKER.swap(true, core::sync::atomic::Ordering::AcqRel)
743 {
744 return;
745 }
746 }
747 }
748 }
749}
750
751pub(crate) fn make_geolocation_error() -> Value {
755 let o = Obj::plain();
756 let mut b = o.borrow_mut();
757 b.props.insert("code".into(), Value::Number(1.0));
758 b.props.insert(
759 "message".into(),
760 Value::str("Geolocation is not supported in this environment"),
761 );
762 b.props.insert("PERMISSION_DENIED".into(), Value::Number(1.0));
763 b.props.insert("POSITION_UNAVAILABLE".into(), Value::Number(2.0));
764 b.props.insert("TIMEOUT".into(), Value::Number(3.0));
765 drop(b);
766 Value::Object(o)
767}
768pub(crate) fn geolocation_get_current_position(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
773 if let Some(err_cb) = callable_or_none(arg(a, 1)) {
774 it.call_listener(
775 &err_cb,
776 Value::Undefined,
777 &[make_geolocation_error()],
778 "geolocation error callback",
779 );
780 }
781 Ok(Value::Undefined)
782}
783pub(crate) fn geolocation_watch_position(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
787 geolocation_get_current_position(it, this, a)?;
788 Ok(Value::Number(TIMER_ID.fetch_add(1, Ordering::Relaxed) as f64))
789}
790pub(crate) fn geolocation_clear_watch(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
792 Ok(Value::Undefined)
793}
794pub(crate) fn permissions_query(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
798 let name = obj_prop(&arg(a, 0), "name").map(|v| v.to_js_string()).unwrap_or_default();
799 let state = match name.as_str() {
800 "geolocation" | "camera" | "microphone" => "denied",
801 "notifications" => "granted",
802 _ => "prompt",
803 };
804 let status = Obj::plain();
805 {
806 let mut b = status.borrow_mut();
807 b.props.insert("state".into(), Value::str(state));
808 b.props.insert("name".into(), Value::str(name));
809 b.props.insert("onchange".into(), Value::Null);
810 }
811 Ok(resolved_promise(it, Value::Object(status)))
812}
813
814pub(crate) fn wake_lock_request(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
818 let ty = arg(a, 0).to_js_string();
819 let ty = if ty.is_empty() { String::from("screen") } else { ty };
820 let sentinel = Obj::plain();
821 {
822 let mut b = sentinel.borrow_mut();
823 b.props.insert("released".into(), Value::Bool(false));
824 b.props.insert("type".into(), Value::str(ty));
825 b.props
826 .insert("release".into(), nv("WakeLockSentinel.release", wake_lock_sentinel_release));
827 }
828 Ok(resolved_promise(it, Value::Object(sentinel)))
829}
830pub(crate) fn wake_lock_sentinel_release(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
832 if let Value::Object(o) = &this {
833 o.borrow_mut().props.insert("released".into(), Value::Bool(true));
834 }
835 Ok(resolved_promise(it, Value::Undefined))
836}
837
838pub(crate) fn navigator_can_share(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
842 Ok(Value::Bool(false))
843}
844pub(crate) fn navigator_share(it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
848 let err = Obj::plain();
849 {
850 let mut b = err.borrow_mut();
851 b.props.insert("name".into(), Value::str("AbortError"));
852 b.props.insert(
853 "message".into(),
854 Value::str("Share was cancelled: no share target available in this environment"),
855 );
856 }
857 Ok(rejected_promise(it, Value::Object(err)))
858}
859
860pub(crate) fn eye_dropper_ctor(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
863 let o = Obj::plain();
864 o.borrow_mut().props.insert("open".into(), nv("EyeDropper.open", eye_dropper_open));
865 Ok(Value::Object(o))
866}
867pub(crate) fn eye_dropper_open(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
871 let err = Obj::plain();
872 {
873 let mut b = err.borrow_mut();
874 b.props.insert("name".into(), Value::str("AbortError"));
875 b.props.insert(
876 "message".into(),
877 Value::str("The color picker was cancelled: no picker UI available in this environment"),
878 );
879 }
880 Ok(rejected_promise(it, Value::Object(err)))
881}
882
883pub(crate) fn navigator_vibrate(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
887 Ok(Value::Bool(true))
888}
889
890pub(crate) fn navigator_locks_request(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
899 let cb = if matches!(arg(a, 2), Value::Object(o) if o.borrow().is_callable()) {
900 arg(a, 2)
901 } else {
902 arg(a, 1)
903 };
904 let name = arg(a, 0).to_js_string();
905 let lock = Obj::plain();
906 {
907 let mut b = lock.borrow_mut();
908 b.props.insert("name".into(), Value::str(name));
909 b.props.insert("mode".into(), Value::str("exclusive"));
910 }
911 let result = it.call_value(&cb, Value::Undefined, &[Value::Object(lock)])?;
912 Ok(resolved_promise(it, result))
913}
914pub(crate) fn navigator_locks_query(it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
917 let o = Obj::plain();
918 {
919 let mut b = o.borrow_mut();
920 b.props.insert("held".into(), Value::Object(Obj::array(alloc::vec![])));
921 b.props.insert("pending".into(), Value::Object(Obj::array(alloc::vec![])));
922 }
923 Ok(resolved_promise(it, Value::Object(o)))
924}
925pub(crate) fn navigator_get_battery(it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
926 let battery = Obj::plain();
927 {
928 let mut b = battery.borrow_mut();
929 b.props.insert("charging".into(), Value::Bool(true));
930 b.props.insert("chargingTime".into(), Value::Number(0.0));
931 b.props.insert("dischargingTime".into(), Value::Number(f64::INFINITY));
932 b.props.insert("level".into(), Value::Number(1.0));
933 b.props
934 .insert("addEventListener".into(), nv("addEventListener", dom_noop));
935 b.props
936 .insert("removeEventListener".into(), nv("removeEventListener", dom_noop));
937 }
938 Ok(resolved_promise(it, Value::Object(battery)))
939}
940
941pub(crate) fn fetch(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
944 let first = arg(a, 0);
945 let is_request_like = matches!(&first, Value::Object(o) if o.borrow().props.contains_key("url") || o.borrow().props.contains_key("_req_url"));
949 let url = if let Some(u) = obj_prop(&first, "_req_url") {
950 u.to_js_string()
951 } else if is_request_like {
952 let u = obj_prop(&first, "url").unwrap_or(Value::Undefined);
953 it.to_string_value(u)?
954 } else {
955 it.to_string_value(first.clone())?
956 };
957 let opts = arg(a, 1);
958 let method = obj_prop(&opts, "method")
959 .or_else(|| if is_request_like { obj_prop(&first, "_req_method").or_else(|| obj_prop(&first, "method")) } else { None })
960 .map(|v| v.to_js_string())
961 .unwrap_or_else(|| String::from("GET"));
962 let body = obj_prop(&opts, "body")
963 .or_else(|| if is_request_like { obj_prop(&first, "_body") } else { None })
964 .map(|v| body_to_bytes(&v))
965 .unwrap_or_default();
966 let headers = obj_prop(&opts, "headers")
967 .or_else(|| if is_request_like { obj_prop(&first, "headers") } else { None })
968 .unwrap_or(Value::Undefined);
969 let content_type =
970 headers_content_type(&headers).unwrap_or_else(|| String::from("text/plain;charset=UTF-8"));
971 let signal_opt = obj_prop(&opts, "signal")
978 .or_else(|| if is_request_like { obj_prop(&first, "signal") } else { None });
979 if let Some(signal) = signal_opt {
980 let already_aborted = matches!(&signal, Value::Object(o) if matches!(o.borrow().props.get("aborted"), Some(Value::Bool(true))));
981 if already_aborted {
982 let reason = match &signal {
983 Value::Object(o) => o.borrow().props.get("reason").cloned().unwrap_or(Value::Undefined),
984 _ => Value::Undefined,
985 };
986 return Ok(rejected_promise(it, reason));
987 }
988 }
989 let base = it.base_url.clone();
990 let resolved_url = resolve_url(&base, &url);
991 let req_headers = headers_list(&headers);
992 match do_http_request_raw(&base, &url, &method, &body, &content_type, &req_headers) {
993 Ok((status, body, bytes)) => {
994 let resp = make_response_with_url(status, body, resolved_url);
995 attach_raw_body(&resp, &bytes);
996 let stream = super::streams::make_readable_stream_from_bytes(it, &bytes);
998 if let Value::Object(r) = &resp {
999 r.borrow_mut().props.insert("body".into(), stream);
1000 }
1001 Ok(resolved_promise(it, resp))
1002 }
1003 Err(msg) => Ok(rejected_promise(it, it.error(format!("fetch: {}", msg)))),
1004 }
1005}
1006
1007pub(crate) fn xhr_ctor(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
1010 let o = Obj::plain();
1011 {
1012 let mut b = o.borrow_mut();
1013 b.props.insert("readyState".into(), Value::Number(0.0));
1014 b.props.insert("status".into(), Value::Number(0.0));
1015 b.props.insert("statusText".into(), Value::str(""));
1016 b.props.insert("responseText".into(), Value::str(""));
1017 b.props.insert("response".into(), Value::str(""));
1018 b.props.insert("open".into(), nv("open", xhr_open));
1019 b.props.insert("send".into(), nv("send", xhr_send));
1020 b.props.insert(
1021 "setRequestHeader".into(),
1022 nv("setRequestHeader", xhr_set_request_header),
1023 );
1024 b.props.insert("abort".into(), nv("abort", xhr_noop));
1025 b.props.insert(
1026 "getAllResponseHeaders".into(),
1027 nv("getAllResponseHeaders", xhr_get_all_response_headers),
1028 );
1029 b.props.insert(
1034 "getResponseHeader".into(),
1035 nv("getResponseHeader", xhr_get_response_header),
1036 );
1037 b.props.insert(
1038 "addEventListener".into(),
1039 nv("addEventListener", xhr_add_event_listener),
1040 );
1041 b.props.insert(
1045 "removeEventListener".into(),
1046 nv("removeEventListener", xhr_remove_event_listener),
1047 );
1048 }
1049 Ok(Value::Object(o))
1050}
1051
1052pub(crate) fn xhr_noop(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
1053 Ok(Value::Undefined)
1054}
1055
1056pub(crate) fn xhr_set_request_header(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1058 if let Value::Object(o) = &this {
1059 let name = arg(a, 0).to_js_string();
1060 let value = arg(a, 1).to_js_string();
1061 if name.eq_ignore_ascii_case("content-type") {
1062 o.borrow_mut().props.insert("_content_type".into(), Value::str(value));
1063 } else {
1064 let hdrs = o.borrow().props.get("_req_headers").cloned();
1066 let hdrs = match hdrs {
1067 Some(Value::Object(h)) => h,
1068 _ => {
1069 let h = Obj::plain();
1070 o.borrow_mut().props.insert("_req_headers".into(), Value::Object(h.clone()));
1071 h
1072 }
1073 };
1074 hdrs.borrow_mut().props.insert(name, Value::str(value));
1075 }
1076 }
1077 Ok(Value::Undefined)
1078}
1079
1080pub(crate) fn xhr_empty_str(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
1081 Ok(Value::str(""))
1082}
1083pub(crate) fn xhr_get_response_header(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1084 let name = arg(a, 0).to_js_string().to_ascii_lowercase();
1085 if let Value::Object(o) = &this {
1086 if let Some(Value::Object(h)) = o.borrow().props.get("_resp_headers") {
1087 for (k, v) in h.borrow().props.iter() {
1088 if k.to_ascii_lowercase() == name {
1089 return Ok(v.clone());
1090 }
1091 }
1092 }
1093 }
1094 Ok(Value::Null)
1095}
1096
1097pub(crate) fn xhr_get_all_response_headers(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1099 let mut out = String::new();
1100 if let Value::Object(o) = &this {
1101 if let Some(Value::Object(h)) = o.borrow().props.get("_resp_headers") {
1102 for (k, v) in h.borrow().props.iter() {
1103 out.push_str(&k.to_ascii_lowercase());
1104 out.push_str(": ");
1105 out.push_str(&v.to_js_string());
1106 out.push_str("\r\n");
1107 }
1108 }
1109 }
1110 Ok(Value::str(out))
1111}
1112
1113pub(crate) fn xhr_add_event_listener(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1115 if let Value::Object(o) = &this {
1116 o.borrow_mut()
1117 .props
1118 .insert(format!("on{}", arg(a, 0).to_js_string()), arg(a, 1));
1119 }
1120 Ok(Value::Undefined)
1121}
1122pub(crate) fn xhr_remove_event_listener(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1126 if let Value::Object(o) = &this {
1127 let key = format!("on{}", arg(a, 0).to_js_string());
1128 let target = arg(a, 1);
1129 let matches = match (o.borrow().props.get(&key), &target) {
1130 (Some(Value::Object(stored)), Value::Object(t)) => Rc::ptr_eq(stored, t),
1131 _ => false,
1132 };
1133 if matches {
1134 o.borrow_mut().props.shift_remove(&key);
1135 }
1136 }
1137 Ok(Value::Undefined)
1138}
1139
1140pub(crate) fn xhr_open(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1141 let url = it.to_string_value(arg(a, 1))?;
1143 if let Value::Object(o) = &this {
1144 let mut b = o.borrow_mut();
1145 b.props
1146 .insert("_method".into(), Value::str(arg(a, 0).to_js_string()));
1147 b.props
1148 .insert("_url".into(), Value::str(url));
1149 b.props.insert("readyState".into(), Value::Number(1.0));
1150 }
1151 Ok(Value::Undefined)
1152}
1153
1154pub(crate) fn xhr_send(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1155 let o = match &this {
1156 Value::Object(o) => o.clone(),
1157 _ => return Ok(Value::Undefined),
1158 };
1159 let (url, method, content_type) = {
1160 let b = o.borrow();
1161 (
1162 b.props
1163 .get("_url")
1164 .map(|v| v.to_js_string())
1165 .unwrap_or_default(),
1166 b.props
1167 .get("_method")
1168 .map(|v| v.to_js_string())
1169 .unwrap_or_else(|| String::from("GET")),
1170 b.props
1171 .get("_content_type")
1172 .map(|v| v.to_js_string())
1173 .unwrap_or_else(|| String::from("text/plain;charset=UTF-8")),
1174 )
1175 };
1176 let req_body = body_to_bytes(&arg(a, 0));
1178 let req_headers = headers_list(&o.borrow().props.get("_req_headers").cloned().unwrap_or(Value::Undefined));
1179 let base = it.base_url.clone();
1180 let (status, body, ok, raw, resp_headers) = match do_http_request_full(&base, &url, &method, &req_body, &content_type, &req_headers) {
1181 Ok((s, b, raw, h)) => (s, b, true, raw, h),
1182 Err(e) => {
1183 crate::warn!("[XHR] 取得失敗 url={:?} 理由={}", url, e);
1186 (0u16, String::new(), false, Vec::new(), alloc::collections::BTreeMap::new())
1187 }
1188 };
1189 {
1190 let h = Obj::plain();
1191 for (k, v) in resp_headers {
1192 h.borrow_mut().props.insert(k, Value::str(v));
1193 }
1194 o.borrow_mut().props.insert("_resp_headers".into(), Value::Object(h));
1195 }
1196 let response_type = o.borrow().props.get("responseType").map(|v| v.to_js_string()).unwrap_or_default();
1199 {
1200 let mut b = o.borrow_mut();
1201 b.props
1202 .insert("status".into(), Value::Number(status as f64));
1203 b.props
1207 .insert("statusText".into(), Value::str(http_status_text(status)));
1208 b.props
1209 .insert("responseText".into(), Value::str(body.clone()));
1210 let response = match response_type.as_str() {
1211 "arraybuffer" => super::typed_array::make_arraybuffer_with_bytes(raw.iter().map(|&x| x as f64).collect()),
1212 "blob" => super::typed_array::make_blob(raw.iter().map(|&x| x as f64).collect(), String::new()),
1213 _ => Value::str(body),
1214 };
1215 b.props.insert("response".into(), response);
1216 b.props.insert("readyState".into(), Value::Number(4.0));
1217 }
1218 let onrsc = o.borrow().props.get("onreadystatechange").cloned();
1219 if let Some(f) = onrsc.and_then(callable_or_none) {
1220 it.call_listener(&f, this.clone(), &[], "XHR onreadystatechange");
1221 }
1222 let cb = o
1223 .borrow()
1224 .props
1225 .get(if ok { "onload" } else { "onerror" })
1226 .cloned();
1227 if let Some(f) = cb.and_then(callable_or_none) {
1228 let ctx = if ok { "XHR onload" } else { "XHR onerror" };
1229 it.call_listener(&f, this.clone(), &[], ctx);
1230 }
1231 Ok(Value::Undefined)
1232}
1233
1234pub(crate) fn promise_all_static(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1236 let items = it.iter_to_vec(&arg(a, 0));
1237 let result = new_pending();
1238 it.run_microtasks();
1240 let mut out: Vec<Value> = Vec::new();
1241 let mut rejected: Option<Value> = None;
1242 for item in items {
1243 match resolve_maybe_thenable(it, &item) {
1248 Some(s) => {
1249 let b = s.borrow();
1250 match b.status {
1251 PromiseStatus::Fulfilled => out.push(b.value.clone()),
1252 PromiseStatus::Rejected => {
1253 rejected = Some(b.value.clone());
1254 break;
1255 }
1256 PromiseStatus::Pending => out.push(Value::Undefined),
1257 }
1258 }
1259 None => out.push(item), }
1261 }
1262 match rejected {
1263 Some(e) => it.promise_reject(&result, e),
1264 None => it.promise_resolve(&result, Value::Object(Obj::array(out))),
1265 }
1266 Ok(Value::Object(Obj::promise(result)))
1267}
1268
1269pub fn promise_method(key: &str) -> Value {
1271 match key {
1272 "then" => nv("Promise.then", promise_then_m),
1273 "catch" => nv("Promise.catch", promise_catch_m),
1274 "finally" => nv("Promise.finally", promise_finally_m),
1275 _ => Value::Undefined,
1276 }
1277}
1278
1279pub(crate) fn promise_then_m(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1280 if let Some(s) = this_promise_state(&this) {
1281 let on_f = callable_or_none(arg(a, 0));
1282 let on_r = callable_or_none(arg(a, 1));
1283 return Ok(it.promise_then(&s, on_f, on_r));
1284 }
1285 Ok(Value::Undefined)
1286}
1287
1288pub(crate) fn promise_catch_m(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1289 if let Some(s) = this_promise_state(&this) {
1290 let on_r = callable_or_none(arg(a, 0));
1291 return Ok(it.promise_then(&s, None, on_r));
1292 }
1293 Ok(Value::Undefined)
1294}
1295
1296pub(crate) fn promise_finally_m(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1305 let Some(s) = this_promise_state(&this) else {
1306 return Ok(Value::Undefined);
1307 };
1308 let cb = arg(a, 0);
1309 if callable_or_none(cb.clone()).is_none() {
1310 return Ok(it.promise_then(&s, None, None));
1312 }
1313 it.global
1314 .borrow_mut()
1315 .vars
1316 .insert("__finally_promise".into(), this);
1317 it.global.borrow_mut().vars.insert("__finally_cb".into(), cb);
1318 let src = "(function(){ \
1319 var p = __finally_promise; \
1320 var cb = __finally_cb; \
1321 return p.then( \
1322 function(v){ return Promise.resolve(cb()).then(function(){ return v; }); }, \
1323 function(r){ return Promise.resolve(cb()).then(function(){ throw r; }); } \
1324 ); \
1325 })()";
1326 let result = it.eval_source(src);
1327 it.global.borrow_mut().vars.remove("__finally_promise");
1328 it.global.borrow_mut().vars.remove("__finally_cb");
1329 result
1330}
1331