1use super::*;
4
5pub(crate) fn window_confirm(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
14 let _ = console_log(it, this, a);
15 Ok(Value::Bool(true))
16}
17pub(crate) fn window_prompt(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
21 let _ = console_log(it, this, a);
22 match a.get(1) {
23 Some(v) if !matches!(v, Value::Undefined) => Ok(v.clone()),
24 _ => Ok(Value::Null),
25 }
26}
27
28pub(crate) fn window_open(_it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
38 Ok(Value::Null)
39}
40pub(crate) fn window_close(_it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
44 Ok(Value::Undefined)
45}
46pub(crate) fn window_stop(_it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
51 Ok(Value::Undefined)
52}
53pub(crate) fn window_focus(_it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
58 Ok(Value::Undefined)
59}
60
61pub(crate) fn window_scroll_to(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
70 let y = if let Some(Value::Object(o)) = a.first() {
71 o.borrow()
72 .props
73 .get("top")
74 .map(|v| v.to_number())
75 .unwrap_or(0.0)
76 } else {
77 arg(a, 1).to_number()
78 };
79 it.dom.borrow_mut().pending_scroll_abs_y = Some(y as i32);
80 Ok(Value::Undefined)
81}
82pub(crate) fn window_scroll_by(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
86 let dy = if let Some(Value::Object(o)) = a.first() {
87 o.borrow()
88 .props
89 .get("top")
90 .map(|v| v.to_number())
91 .unwrap_or(0.0)
92 } else {
93 arg(a, 1).to_number()
94 };
95 let mut dom = it.dom.borrow_mut();
96 dom.pending_scroll_by_y = Some(dom.pending_scroll_by_y.unwrap_or(0) + dy as i32);
97 Ok(Value::Undefined)
98}
99pub(crate) fn element_scroll_into_view(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
100 Ok(Value::Undefined) }
102
103pub(crate) fn window_match_media(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
105 let query = arg(a, 0).to_js_string();
106 let matches = crate::os_lib::css::media_query_matches(&query);
113 let mql = Obj::plain();
114 {
115 let mut b = mql.borrow_mut();
116 b.props.insert("matches".into(), Value::Bool(matches));
117 b.props.insert("media".into(), Value::str(query));
118 b.props
119 .insert("addEventListener".into(), nv("addEventListener", dom_noop));
120 b.props
121 .insert("addListener".into(), nv("addListener", dom_noop));
122 b.props
123 .insert("removeListener".into(), nv("removeListener", dom_noop));
124 b.props.insert(
125 "removeEventListener".into(),
126 nv("removeEventListener", dom_noop),
127 );
128 }
129 Ok(Value::Object(mql))
130}
131
132pub(crate) fn push_window_listener(it: &mut Interp, key: &str, cb: Value, signal: Option<Value>) {
142 let w = match it.global.borrow().vars.get("window") {
143 Some(Value::Object(w)) => w.clone(),
144 _ => return,
145 };
146 let entry = Value::Object(Obj::array(alloc::vec![cb, signal.unwrap_or(Value::Undefined)]));
147 let existing = w.borrow().props.get(key).cloned();
148 match existing {
149 Some(Value::Object(arr)) if matches!(arr.borrow().kind, ObjKind::Array(_)) => {
150 if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
151 items.push(entry);
152 }
153 }
154 _ => {
155 let arr = Obj::array(alloc::vec![entry]);
156 w.borrow_mut().props.insert(key.into(), Value::Object(arr));
157 }
158 }
159}
160pub(crate) fn window_listener_aborted(entry: &Value) -> bool {
163 if let Value::Object(o) = entry {
164 if let ObjKind::Array(items) = &o.borrow().kind {
165 if let Some(sig) = items.get(1) {
166 return super::super::dom_bridge::is_signal_aborted(sig);
167 }
168 }
169 }
170 false
171}
172
173pub(crate) fn window_add_event_listener(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
178 let event = arg(a, 0).to_js_string();
179 let cb = arg(a, 1);
180 let signal = parse_listener_signal(&arg(a, 2));
184 if signal.as_ref().is_some_and(super::super::dom_bridge::is_signal_aborted) {
185 return Ok(Value::Undefined);
186 }
187 match event.as_str() {
188 "load" | "DOMContentLoaded" | "readystatechange" => {
192 let state = match it.global.borrow().vars.get("document").cloned() {
196 Some(d) => obj_prop(&d, "readyState").map(|v| v.to_js_string()).unwrap_or_default(),
197 None => String::new(),
198 };
199 let reached = match event.as_str() {
200 "DOMContentLoaded" => state != "loading",
201 _ => state.is_empty() || state == "complete",
202 };
203 crate::warn!("[TMPRS] add {} state={} reached={}", event, state, reached); if reached {
205 it.call_listener(&cb, Value::Undefined, &[], "window load listener");
207 } else {
208 let key = alloc::format!("_evt_{}_listeners", event);
209 push_window_listener(it, &key, cb, signal);
210 }
211 }
212 "scroll" => {
213 push_window_listener(it, "_scroll_listeners", cb, signal);
214 }
215 "popstate" => {
216 push_window_listener(it, "_popstate_listeners", cb, signal);
217 }
218 "hashchange" => {
219 push_window_listener(it, "_hashchange_listeners", cb, signal);
220 }
221 "resize" => {
222 push_window_listener(it, "_resize_listeners", cb, signal);
223 }
224 "online" => {
231 push_window_listener(it, "_online_listeners", cb, signal);
232 }
233 "offline" => {
234 push_window_listener(it, "_offline_listeners", cb, signal);
235 }
236 "beforeunload" => {
242 push_window_listener(it, "_beforeunload_listeners", cb, signal);
243 }
244 "unload" => {
245 push_window_listener(it, "_unload_listeners", cb, signal);
246 }
247 "pageshow" => {
248 push_window_listener(it, "_pageshow_listeners", cb, signal);
249 }
250 "pagehide" => {
251 push_window_listener(it, "_pagehide_listeners", cb, signal);
252 }
253 "visibilitychange" => {
254 push_window_listener(it, "_visibilitychange_listeners", cb, signal);
255 }
256 "languagechange" => {
257 push_window_listener(it, "_languagechange_listeners", cb, signal);
258 }
259 "storage" => {
265 push_window_listener(it, "_storage_listeners", cb, signal);
266 }
267 "beforeprint" => {
272 push_window_listener(it, "_beforeprint_listeners", cb, signal);
273 }
274 "afterprint" => {
275 push_window_listener(it, "_afterprint_listeners", cb, signal);
276 }
277 "message" => {
280 push_window_listener(it, "_message_listeners", cb, signal);
281 }
282 "error" => {
285 push_window_listener(it, "_error_listeners", cb, signal);
286 }
287 other => {
290 let key = alloc::format!("_evt_{}_listeners", other);
291 push_window_listener(it, &key, cb, signal);
292 }
293 }
294 Ok(Value::Undefined)
295}
296
297pub(crate) fn window_remove_event_listener(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
304 let event = arg(a, 0).to_js_string();
305 let cb = arg(a, 1);
306 let custom_key = alloc::format!("_evt_{}_listeners", event);
307 let key = match event.as_str() {
308 "scroll" => "_scroll_listeners",
309 "popstate" => "_popstate_listeners",
310 "hashchange" => "_hashchange_listeners",
311 "resize" => "_resize_listeners",
312 "online" => "_online_listeners",
313 "offline" => "_offline_listeners",
314 "beforeunload" => "_beforeunload_listeners",
315 "unload" => "_unload_listeners",
316 "pageshow" => "_pageshow_listeners",
317 "pagehide" => "_pagehide_listeners",
318 "visibilitychange" => "_visibilitychange_listeners",
319 "languagechange" => "_languagechange_listeners",
320 "storage" => "_storage_listeners",
321 "beforeprint" => "_beforeprint_listeners",
322 "afterprint" => "_afterprint_listeners",
323 "message" => "_message_listeners",
324 "error" => "_error_listeners",
325 _ => custom_key.as_str(),
326 };
327 let w = match it.global.borrow().vars.get("window") {
328 Some(Value::Object(w)) => w.clone(),
329 _ => return Ok(Value::Undefined),
330 };
331 let arr = match w.borrow().props.get(key) {
332 Some(Value::Object(arr)) => arr.clone(),
333 _ => return Ok(Value::Undefined),
334 };
335 if let ObjKind::Array(items) = &mut arr.borrow_mut().kind {
336 items.retain(|entry| {
337 let stored_cb = match entry {
338 Value::Object(o) => match &o.borrow().kind {
339 ObjKind::Array(pair) => pair.first().cloned().unwrap_or(Value::Undefined),
340 _ => entry.clone(),
341 },
342 _ => entry.clone(),
343 };
344 match (&stored_cb, &cb) {
345 (Value::Object(x), Value::Object(y)) => !Rc::ptr_eq(x, y),
346 _ => true,
347 }
348 });
349 }
350 Ok(Value::Undefined)
351}
352pub(crate) fn window_dispatch_event(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
366 let event = arg(a, 0);
367 let event_type = obj_prop(&event, "type").map(|v| v.to_js_string()).unwrap_or_default();
368 let custom_key = alloc::format!("_evt_{}_listeners", event_type);
369 let key = match event_type.as_str() {
370 "scroll" => "_scroll_listeners",
371 "popstate" => "_popstate_listeners",
372 "hashchange" => "_hashchange_listeners",
373 "resize" => "_resize_listeners",
374 "online" => "_online_listeners",
375 "offline" => "_offline_listeners",
376 "beforeunload" => "_beforeunload_listeners",
377 "unload" => "_unload_listeners",
378 "pageshow" => "_pageshow_listeners",
379 "pagehide" => "_pagehide_listeners",
380 "visibilitychange" => "_visibilitychange_listeners",
381 "languagechange" => "_languagechange_listeners",
382 "storage" => "_storage_listeners",
383 "beforeprint" => "_beforeprint_listeners",
384 "afterprint" => "_afterprint_listeners",
385 "message" => "_message_listeners",
386 "error" => "_error_listeners",
387 _ => custom_key.as_str(),
388 };
389 let w = match it.global.borrow().vars.get("window") {
390 Some(Value::Object(w)) => w.clone(),
391 _ => return Ok(Value::Bool(true)),
392 };
393 let target = if matches!(this, Value::Object(_)) {
399 this.clone()
400 } else {
401 Value::Object(w.clone())
402 };
403 if let Value::Object(eo) = &event {
404 let mut e = eo.borrow_mut();
405 e.props.insert("target".into(), target.clone());
406 e.props.insert("srcElement".into(), target.clone());
407 e.props.insert("currentTarget".into(), target);
408 e.props.insert("eventPhase".into(), Value::Number(2.0));
410 }
411 let on_handler = w.borrow().props.get(&alloc::format!("on{event_type}")).cloned();
412 if let Some(f) = on_handler.and_then(callable_or_none) {
413 if event_type == "error" {
422 let (message, filename, lineno, colno, err) = if let Value::Object(eo) = &event {
423 let b = eo.borrow();
424 (
425 b.props.get("message").cloned().unwrap_or_else(|| Value::str("")),
426 b.props.get("filename").cloned().unwrap_or_else(|| Value::str("")),
427 b.props.get("lineno").cloned().unwrap_or(Value::Number(0.0)),
428 b.props.get("colno").cloned().unwrap_or(Value::Number(0.0)),
429 b.props.get("error").cloned().unwrap_or(Value::Null),
430 )
431 } else {
432 (Value::str(""), Value::str(""), Value::Number(0.0), Value::Number(0.0), Value::Null)
433 };
434 it.call_listener(
435 &f,
436 Value::Undefined,
437 &[message, filename, lineno, colno, err],
438 "window.onerror",
439 );
440 } else {
441 it.call_listener(
442 &f,
443 Value::Undefined,
444 core::slice::from_ref(&event),
445 "window.onerror",
446 );
447 }
448 }
449 let entries = match w.borrow().props.get(key) {
450 Some(Value::Object(arr)) => match &arr.borrow().kind {
451 ObjKind::Array(items) => items.clone(),
452 _ => alloc::vec::Vec::new(),
453 },
454 _ => alloc::vec::Vec::new(),
455 };
456 for entry in entries {
457 let (cb, signal) = match &entry {
458 Value::Object(o) => match &o.borrow().kind {
459 ObjKind::Array(items) => (items.first().cloned().unwrap_or(Value::Undefined), items.get(1).cloned()),
460 _ => (entry.clone(), None),
461 },
462 _ => (entry.clone(), None),
463 };
464 if signal.as_ref().is_some_and(super::super::dom_bridge::is_signal_aborted) {
465 continue;
466 }
467 it.call_listener(
468 &cb,
469 Value::Undefined,
470 core::slice::from_ref(&event),
471 "window listener",
472 );
473 }
474 if let Value::Object(eo) = &event {
477 let mut e = eo.borrow_mut();
478 e.props.insert("currentTarget".into(), Value::Null);
479 e.props.insert("eventPhase".into(), Value::Number(0.0));
480 }
481 let default_prevented = if let Value::Object(eo) = &event {
487 matches!(eo.borrow().props.get("defaultPrevented"), Some(Value::Bool(true)))
488 } else {
489 false
490 };
491 Ok(Value::Bool(!default_prevented))
492}
493
494fn is_window_shared_event_type(t: &str) -> bool {
498 matches!(
499 t,
500 "load"
501 | "DOMContentLoaded"
502 | "readystatechange"
503 | "scroll"
504 | "popstate"
505 | "hashchange"
506 | "resize"
507 | "online"
508 | "offline"
509 | "beforeunload"
510 | "unload"
511 | "pageshow"
512 | "pagehide"
513 | "visibilitychange"
514 | "languagechange"
515 | "storage"
516 | "beforeprint"
517 | "afterprint"
518 | "message"
519 | "error"
520 )
521}
522
523pub(crate) fn document_add_event_listener(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
535 let etype = arg(a, 0).to_js_string();
536 { static N: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); if N.fetch_add(1, core::sync::atomic::Ordering::Relaxed) < 60 { crate::warn!("[TMPRS] doc.add {} stack={:?}", etype, it.call_names.iter().rev().take(3).collect::<Vec<_>>()); } } if is_window_shared_event_type(&etype) {
538 return window_add_event_listener(it, this, a);
539 }
540 event_target_add_event_listener(it, this, a)
541}
542pub(crate) fn document_remove_event_listener(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
545 let etype = arg(a, 0).to_js_string();
546 if is_window_shared_event_type(&etype) {
547 return window_remove_event_listener(it, this, a);
548 }
549 event_target_remove_event_listener(it, this, a)
550}
551pub(crate) fn document_dispatch_event(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
557 let event = arg(a, 0);
558 let etype = obj_prop(&event, "type").map(|v| v.to_js_string()).unwrap_or_default();
559 if is_window_shared_event_type(&etype) {
560 return window_dispatch_event(it, this, a);
561 }
562 event_target_dispatch_event(it, this, a)
563}
564
565pub fn dom_request_fullscreen(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
570 if let Some(idx) = this_dom_idx(&this) {
571 {
572 let mut dom = it.dom.borrow_mut();
573 let len = dom.nodes.len();
574 for i in 0..len {
575 dom.remove_attr(i, "_fullscreen");
576 }
577 dom.set_attr(idx, "_fullscreen", "1");
578 }
579 if let Some(Value::Object(doc)) = it.global.borrow().vars.get("document").cloned() {
580 let mut d = doc.borrow_mut();
581 d.props.insert("fullscreenElement".into(), this.clone());
582 d.props.insert("webkitFullscreenElement".into(), this.clone());
583 d.props.insert("mozFullScreenElement".into(), this.clone());
584 d.props.insert("msFullscreenElement".into(), this.clone());
585 }
586 fire_fullscreen_change_events(it, idx);
587 }
588 Ok(resolved_promise(it, Value::Undefined))
589}
590
591pub(crate) fn document_exit_fullscreen(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
593 let mut target_idx: Option<usize> = None;
594 if let Some(Value::Object(doc)) = it.global.borrow().vars.get("document").cloned() {
595 let prev = doc.borrow().props.get("fullscreenElement").cloned();
596 if let Some(prev_val) = prev {
597 if let Some(idx) = this_dom_idx(&prev_val) {
598 target_idx = Some(idx);
599 }
600 }
601 let mut d = doc.borrow_mut();
602 d.props.insert("fullscreenElement".into(), Value::Null);
603 d.props.insert("webkitFullscreenElement".into(), Value::Null);
604 d.props.insert("mozFullScreenElement".into(), Value::Null);
605 d.props.insert("msFullscreenElement".into(), Value::Null);
606 }
607 {
608 let mut dom = it.dom.borrow_mut();
609 let len = dom.nodes.len();
610 for i in 0..len {
611 if dom.has_attr(i, "_fullscreen") {
612 if target_idx.is_none() {
613 target_idx = Some(i);
614 }
615 dom.remove_attr(i, "_fullscreen");
616 }
617 }
618 }
619 let idx = target_idx.unwrap_or(0);
620 fire_fullscreen_change_events(it, idx);
621 Ok(resolved_promise(it, Value::Undefined))
622}
623
624fn fire_fullscreen_change_events(it: &mut Interp, idx: usize) {
625 let has_node = idx < it.dom.borrow().nodes.len();
626 if has_node {
627 let _ = it.dispatch_event_in_interp(idx, "fullscreenchange", &[]);
628 let _ = it.dispatch_event_in_interp(idx, "webkitfullscreenchange", &[]);
629 }
630 let doc_opt = it.global.borrow().vars.get("document").cloned();
631 if let Some(Value::Object(doc)) = doc_opt {
632 let doc_val = Value::Object(doc.clone());
633 for (ev_type, prop_name) in [
634 ("fullscreenchange", "onfullscreenchange"),
635 ("webkitfullscreenchange", "onwebkitfullscreenchange"),
636 ] {
637 let handler = doc.borrow().props.get(prop_name).cloned();
638 if let Some(h) = handler.filter(|h| matches!(h, Value::Object(o) if o.borrow().is_callable())) {
639 let ev = Obj::plain();
640 {
641 let mut e = ev.borrow_mut();
642 e.props.insert("type".into(), Value::str(ev_type));
643 e.props.insert("target".into(), doc_val.clone());
644 e.props.insert("currentTarget".into(), doc_val.clone());
645 e.props.insert("bubbles".into(), Value::Bool(true));
646 }
647 it.call_listener(&h, doc_val.clone(), &[Value::Object(ev)], prop_name);
648 }
649 }
650 }
651}
652
653pub fn dom_request_pointer_lock(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
658 if this_dom_idx(&this).is_some() {
659 if let Some(Value::Object(doc)) = it.global.borrow().vars.get("document").cloned() {
660 doc.borrow_mut().props.insert("pointerLockElement".into(), this.clone());
661 }
662 }
663 Ok(Value::Undefined)
664}
665pub(crate) fn document_exit_pointer_lock(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
667 if let Some(Value::Object(doc)) = it.global.borrow().vars.get("document").cloned() {
668 doc.borrow_mut().props.insert("pointerLockElement".into(), Value::Null);
669 }
670 Ok(Value::Undefined)
671}
672
673pub fn dom_request_picture_in_picture(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
678 if this_dom_idx(&this).is_some() {
679 if let Some(Value::Object(doc)) = it.global.borrow().vars.get("document").cloned() {
680 doc.borrow_mut()
681 .props
682 .insert("pictureInPictureElement".into(), this.clone());
683 }
684 }
685 Ok(resolved_promise(it, Value::Object(Obj::plain())))
686}
687pub(crate) fn document_exit_picture_in_picture(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
690 if let Some(Value::Object(doc)) = it.global.borrow().vars.get("document").cloned() {
691 doc.borrow_mut()
692 .props
693 .insert("pictureInPictureElement".into(), Value::Null);
694 }
695 Ok(resolved_promise(it, Value::Undefined))
696}
697
698pub(crate) fn document_create_tree_walker(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
703 let root = arg(a, 0);
704 let root_idx = this_dom_idx(&root).unwrap_or(0);
705 let tw = Obj::plain();
706 {
707 let mut b = tw.borrow_mut();
708 b.props.insert("root".into(), root.clone());
709 b.props.insert("currentNode".into(), root);
710 b.props.insert("_tw_root".into(), Value::Number(root_idx as f64));
711 b.props.insert(
712 "whatToShow".into(),
713 if a.len() > 1 { arg(a, 1) } else { Value::Number(0xFFFF_FFFFu32 as f64) },
714 );
715 b.props.insert("filter".into(), arg(a, 2));
716 b.props.insert("nextNode".into(), nv("nextNode", tree_walker_next_node));
717 b.props.insert("previousNode".into(), nv("previousNode", tree_walker_previous_node));
718 b.props.insert("parentNode".into(), nv("parentNode", tree_walker_parent_node));
719 b.props.insert("firstChild".into(), nv("firstChild", tree_walker_first_child));
720 b.props.insert("lastChild".into(), nv("lastChild", tree_walker_last_child));
721 b.props.insert("nextSibling".into(), nv("nextSibling", tree_walker_next_sibling));
722 b.props
723 .insert("previousSibling".into(), nv("previousSibling", tree_walker_previous_sibling));
724 }
725 Ok(Value::Object(tw))
726}
727pub(crate) fn tw_current_idx(this: &Value) -> Option<usize> {
728 let Value::Object(o) = this else { return None };
729 let cur = o.borrow().props.get("currentNode").cloned();
730 cur.and_then(|v| this_dom_idx(&v))
731}
732pub(crate) fn tw_root_idx(this: &Value) -> Option<usize> {
733 let Value::Object(o) = this else { return None };
734 o.borrow().props.get("_tw_root").map(|v| v.to_number() as usize)
735}
736pub(crate) fn tw_set_current(this: &Value, idx: usize) {
737 if let Value::Object(o) = this {
738 o.borrow_mut().props.insert("currentNode".into(), Value::Object(Obj::dom(idx)));
739 }
740}
741pub(crate) fn tw_what_to_show(this: &Value) -> u32 {
742 let Value::Object(o) = this else { return crate::os_lib::dom_traversal::SHOW_ALL };
743 o.borrow()
744 .props
745 .get("whatToShow")
746 .map(|v| v.to_number() as u32)
747 .unwrap_or(crate::os_lib::dom_traversal::SHOW_ALL)
748}
749pub(crate) fn tw_get_filter(this: &Value) -> Option<Value> {
750 let Value::Object(o) = this else { return None };
751 let f = o.borrow().props.get("filter").cloned()?;
752 match f {
753 Value::Null | Value::Undefined => None,
754 _ => Some(f),
755 }
756}
757
758struct DomTreeRef(alloc::rc::Rc<core::cell::RefCell<crate::os_lib::js::dom_bridge::DomBridge>>);
759
760impl crate::os_lib::dom_traversal::TraversalTree for DomTreeRef {
761 fn parent(&self, idx: usize) -> Option<usize> {
762 self.0.borrow().nodes.get(idx).and_then(|n| n.parent)
763 }
764
765 fn first_child(&self, idx: usize) -> Option<usize> {
766 self.0.borrow().nodes.get(idx).and_then(|n| n.children.first().copied())
767 }
768
769 fn last_child(&self, idx: usize) -> Option<usize> {
770 self.0.borrow().nodes.get(idx).and_then(|n| n.children.last().copied())
771 }
772
773 fn next_sibling(&self, idx: usize) -> Option<usize> {
774 let b = self.0.borrow();
775 let p = b.nodes.get(idx).and_then(|n| n.parent)?;
776 let kids = &b.nodes.get(p)?.children;
777 let pos = kids.iter().position(|&c| c == idx)?;
778 kids.get(pos + 1).copied()
779 }
780
781 fn previous_sibling(&self, idx: usize) -> Option<usize> {
782 let b = self.0.borrow();
783 let p = b.nodes.get(idx).and_then(|n| n.parent)?;
784 let kids = &b.nodes.get(p)?.children;
785 let pos = kids.iter().position(|&c| c == idx)?;
786 if pos > 0 {
787 kids.get(pos - 1).copied()
788 } else {
789 None
790 }
791 }
792
793 fn node_type(&self, idx: usize) -> u16 {
794 let b = self.0.borrow();
795 if let Some(n) = b.nodes.get(idx) {
796 if n.is_text {
797 3
798 } else if n.tag == "#comment" {
799 8
800 } else if n.tag == "#document-fragment" || n.tag == "#fragment" {
801 11
802 } else if n.tag == "#document" {
803 9
804 } else {
805 1
806 }
807 } else {
808 1
809 }
810 }
811}
812
813fn run_traversal_filter(
814 it: &mut Interp,
815 filter_val: &Option<Value>,
816 node_idx: usize,
817) -> crate::os_lib::dom_traversal::FilterResult {
818 let Some(filter) = filter_val else {
819 return crate::os_lib::dom_traversal::FilterResult::Accept;
820 };
821 let node_obj = Value::Object(Obj::dom(node_idx));
822 match filter {
823 Value::Object(o) if o.borrow().is_callable() => {
824 match it.call_value(filter, Value::Undefined, &[node_obj]) {
825 Ok(v) => crate::os_lib::dom_traversal::FilterResult::from_u32(v.to_number() as u32),
826 Err(_) => crate::os_lib::dom_traversal::FilterResult::Reject,
827 }
828 }
829 Value::Object(o) => {
830 let accept = o.borrow().props.get("acceptNode").cloned();
831 if let Some(f) = accept {
832 let is_call = match &f {
833 Value::Object(fo) => fo.borrow().is_callable(),
834 _ => false,
835 };
836 if is_call {
837 match it.call_value(&f, filter.clone(), &[node_obj]) {
838 Ok(v) => {
839 crate::os_lib::dom_traversal::FilterResult::from_u32(v.to_number() as u32)
840 }
841 Err(_) => crate::os_lib::dom_traversal::FilterResult::Reject,
842 }
843 } else {
844 crate::os_lib::dom_traversal::FilterResult::Accept
845 }
846 } else {
847 crate::os_lib::dom_traversal::FilterResult::Accept
848 }
849 }
850 _ => crate::os_lib::dom_traversal::FilterResult::Accept,
851 }
852}
853
854pub(crate) fn tree_walker_next_node(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
856 let Some(cur) = tw_current_idx(&this) else { return Ok(Value::Null) };
857 let root = tw_root_idx(&this).unwrap_or(cur);
858 let what_to_show = tw_what_to_show(&this);
859 let filter = tw_get_filter(&this);
860
861 let tree = DomTreeRef(it.dom.clone());
862 let res = crate::os_lib::dom_traversal::tw_next_node(&tree, root, cur, what_to_show, &mut |idx| {
863 run_traversal_filter(it, &filter, idx)
864 });
865
866 if let Some(next) = res {
867 tw_set_current(&this, next);
868 Ok(Value::Object(Obj::dom(next)))
869 } else {
870 Ok(Value::Null)
871 }
872}
873
874pub(crate) fn tree_walker_previous_node(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
876 let Some(cur) = tw_current_idx(&this) else { return Ok(Value::Null) };
877 let root = tw_root_idx(&this).unwrap_or(cur);
878 let what_to_show = tw_what_to_show(&this);
879 let filter = tw_get_filter(&this);
880
881 let tree = DomTreeRef(it.dom.clone());
882 let res = crate::os_lib::dom_traversal::tw_previous_node(&tree, root, cur, what_to_show, &mut |idx| {
883 run_traversal_filter(it, &filter, idx)
884 });
885
886 if let Some(prev) = res {
887 tw_set_current(&this, prev);
888 Ok(Value::Object(Obj::dom(prev)))
889 } else {
890 Ok(Value::Null)
891 }
892}
893
894pub(crate) fn tree_walker_parent_node(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
896 let Some(cur) = tw_current_idx(&this) else { return Ok(Value::Null) };
897 let root = tw_root_idx(&this).unwrap_or(cur);
898 let what_to_show = tw_what_to_show(&this);
899 let filter = tw_get_filter(&this);
900
901 let tree = DomTreeRef(it.dom.clone());
902 let res = crate::os_lib::dom_traversal::tw_parent_node(&tree, root, cur, what_to_show, &mut |idx| {
903 run_traversal_filter(it, &filter, idx)
904 });
905
906 if let Some(p) = res {
907 tw_set_current(&this, p);
908 Ok(Value::Object(Obj::dom(p)))
909 } else {
910 Ok(Value::Null)
911 }
912}
913
914pub(crate) fn tree_walker_first_child(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
916 let Some(cur) = tw_current_idx(&this) else { return Ok(Value::Null) };
917 let what_to_show = tw_what_to_show(&this);
918 let filter = tw_get_filter(&this);
919
920 let tree = DomTreeRef(it.dom.clone());
921 let res = crate::os_lib::dom_traversal::tw_first_child(&tree, cur, what_to_show, &mut |idx| {
922 run_traversal_filter(it, &filter, idx)
923 });
924
925 if let Some(c) = res {
926 tw_set_current(&this, c);
927 Ok(Value::Object(Obj::dom(c)))
928 } else {
929 Ok(Value::Null)
930 }
931}
932
933pub(crate) fn tree_walker_last_child(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
935 let Some(cur) = tw_current_idx(&this) else { return Ok(Value::Null) };
936 let what_to_show = tw_what_to_show(&this);
937 let filter = tw_get_filter(&this);
938
939 let tree = DomTreeRef(it.dom.clone());
940 let res = crate::os_lib::dom_traversal::tw_last_child(&tree, cur, what_to_show, &mut |idx| {
941 run_traversal_filter(it, &filter, idx)
942 });
943
944 if let Some(c) = res {
945 tw_set_current(&this, c);
946 Ok(Value::Object(Obj::dom(c)))
947 } else {
948 Ok(Value::Null)
949 }
950}
951
952pub(crate) fn tree_walker_next_sibling(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
954 let Some(cur) = tw_current_idx(&this) else { return Ok(Value::Null) };
955 let root = tw_root_idx(&this).unwrap_or(cur);
956 let what_to_show = tw_what_to_show(&this);
957 let filter = tw_get_filter(&this);
958
959 let tree = DomTreeRef(it.dom.clone());
960 let res = crate::os_lib::dom_traversal::tw_next_sibling(&tree, root, cur, what_to_show, &mut |idx| {
961 run_traversal_filter(it, &filter, idx)
962 });
963
964 if let Some(s) = res {
965 tw_set_current(&this, s);
966 Ok(Value::Object(Obj::dom(s)))
967 } else {
968 Ok(Value::Null)
969 }
970}
971
972pub(crate) fn tree_walker_previous_sibling(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
974 let Some(cur) = tw_current_idx(&this) else { return Ok(Value::Null) };
975 let root = tw_root_idx(&this).unwrap_or(cur);
976 let what_to_show = tw_what_to_show(&this);
977 let filter = tw_get_filter(&this);
978
979 let tree = DomTreeRef(it.dom.clone());
980 let res = crate::os_lib::dom_traversal::tw_previous_sibling(&tree, root, cur, what_to_show, &mut |idx| {
981 run_traversal_filter(it, &filter, idx)
982 });
983
984 if let Some(s) = res {
985 tw_set_current(&this, s);
986 Ok(Value::Object(Obj::dom(s)))
987 } else {
988 Ok(Value::Null)
989 }
990}
991
992pub(crate) fn document_create_node_iterator(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
994 let root = arg(a, 0);
995 let root_idx = this_dom_idx(&root).unwrap_or(0);
996 let ni = Obj::plain();
997 {
998 let mut b = ni.borrow_mut();
999 b.props.insert("root".into(), root.clone());
1000 b.props.insert("referenceNode".into(), root);
1001 b.props.insert("pointerBeforeReferenceNode".into(), Value::Bool(true));
1002 b.props.insert("_ni_root".into(), Value::Number(root_idx as f64));
1003 b.props.insert(
1004 "whatToShow".into(),
1005 if a.len() > 1 { arg(a, 1) } else { Value::Number(0xFFFF_FFFFu32 as f64) },
1006 );
1007 b.props.insert("filter".into(), arg(a, 2));
1008 b.props
1009 .insert("nextNode".into(), nv("nextNode", node_iterator_next_node));
1010 b.props.insert(
1011 "previousNode".into(),
1012 nv("previousNode", node_iterator_previous_node),
1013 );
1014 b.props.insert("detach".into(), nv("detach", dom_noop));
1015 }
1016 Ok(Value::Object(ni))
1017}
1018pub(crate) fn ni_ref_idx(this: &Value) -> Option<usize> {
1019 let Value::Object(o) = this else { return None };
1020 let r = o.borrow().props.get("referenceNode").cloned();
1021 r.and_then(|v| this_dom_idx(&v))
1022}
1023pub(crate) fn ni_root_idx(this: &Value) -> Option<usize> {
1024 let Value::Object(o) = this else { return None };
1025 o.borrow()
1026 .props
1027 .get("_ni_root")
1028 .map(|v| v.to_number() as usize)
1029}
1030pub(crate) fn ni_pointer_before(this: &Value) -> bool {
1031 let Value::Object(o) = this else { return false };
1032 matches!(
1033 o.borrow().props.get("pointerBeforeReferenceNode"),
1034 Some(Value::Bool(true))
1035 )
1036}
1037pub(crate) fn ni_set_state(this: &Value, idx: usize, before: bool) {
1038 if let Value::Object(o) = this {
1039 let mut b = o.borrow_mut();
1040 b.props
1041 .insert("referenceNode".into(), Value::Object(Obj::dom(idx)));
1042 b.props
1043 .insert("pointerBeforeReferenceNode".into(), Value::Bool(before));
1044 }
1045}
1046
1047pub(crate) fn node_iterator_next_node(it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1049 let Some(cur) = ni_ref_idx(&this) else {
1050 return Ok(Value::Null);
1051 };
1052 let root = ni_root_idx(&this).unwrap_or(cur);
1053 let what_to_show = tw_what_to_show(&this);
1054 let filter = tw_get_filter(&this);
1055
1056 if ni_pointer_before(&this) {
1057 ni_set_state(&this, cur, false);
1058 let tree = DomTreeRef(it.dom.clone());
1059 if crate::os_lib::dom_traversal::evaluate_node_filter(&tree, cur, what_to_show, &mut |idx| {
1060 run_traversal_filter(it, &filter, idx)
1061 }) == crate::os_lib::dom_traversal::FilterResult::Accept {
1062 return Ok(Value::Object(Obj::dom(cur)));
1063 }
1064 }
1065
1066 let tree = DomTreeRef(it.dom.clone());
1067 let next = crate::os_lib::dom_traversal::ni_step_next(&tree, root, cur, what_to_show, &mut |idx| {
1068 run_traversal_filter(it, &filter, idx)
1069 });
1070
1071 match next {
1072 Some(n) => {
1073 ni_set_state(&this, n, false);
1074 Ok(Value::Object(Obj::dom(n)))
1075 }
1076 None => Ok(Value::Null),
1077 }
1078}
1079
1080pub(crate) fn node_iterator_previous_node(
1082 it: &mut Interp,
1083 this: Value,
1084 _a: &[Value],
1085) -> Result<Value, Value> {
1086 let Some(cur) = ni_ref_idx(&this) else {
1087 return Ok(Value::Null);
1088 };
1089 let root = ni_root_idx(&this).unwrap_or(cur);
1090 let what_to_show = tw_what_to_show(&this);
1091 let filter = tw_get_filter(&this);
1092
1093 if !ni_pointer_before(&this) {
1094 ni_set_state(&this, cur, true);
1095 let tree = DomTreeRef(it.dom.clone());
1096 if crate::os_lib::dom_traversal::evaluate_node_filter(&tree, cur, what_to_show, &mut |idx| {
1097 run_traversal_filter(it, &filter, idx)
1098 }) == crate::os_lib::dom_traversal::FilterResult::Accept {
1099 return Ok(Value::Object(Obj::dom(cur)));
1100 }
1101 }
1102
1103 let tree = DomTreeRef(it.dom.clone());
1104 let prev = crate::os_lib::dom_traversal::ni_step_previous(&tree, root, cur, what_to_show, &mut |idx| {
1105 run_traversal_filter(it, &filter, idx)
1106 });
1107
1108 match prev {
1109 Some(p) => {
1110 ni_set_state(&this, p, true);
1111 Ok(Value::Object(Obj::dom(p)))
1112 }
1113 None => Ok(Value::Null),
1114 }
1115}
1116
1117pub(crate) fn document_has_storage_access(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
1121 Ok(resolved_promise(it, Value::Bool(true)))
1122}
1123pub(crate) fn document_request_storage_access(it: &mut Interp, _this: Value, _a: &[Value]) -> Result<Value, Value> {
1126 Ok(resolved_promise(it, Value::Undefined))
1127}
1128
1129pub(crate) fn screen_orientation_lock(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1134 if let Value::Object(o) = &this {
1135 o.borrow_mut().props.insert("type".into(), Value::str(arg(a, 0).to_js_string()));
1136 }
1137 Ok(resolved_promise(it, Value::Undefined))
1138}
1139pub(crate) fn screen_orientation_unlock(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1143 if let Value::Object(o) = &this {
1144 let vw = super::super::super::layout::VIEWPORT_HINT_W.load(core::sync::atomic::Ordering::Relaxed);
1145 let vh = super::super::super::layout::VIEWPORT_HINT_H.load(core::sync::atomic::Ordering::Relaxed);
1146 let otype = if vw >= vh { "landscape-primary" } else { "portrait-primary" };
1147 o.borrow_mut().props.insert("type".into(), Value::str(otype));
1148 }
1149 Ok(Value::Undefined)
1150}
1151
1152pub(crate) fn window_post_message(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1156 let data = structured_clone(it, Value::Undefined, a)?;
1157 let href = location_href(&it.global).unwrap_or_default();
1158 let origin = location_components(&href)
1159 .into_iter()
1160 .find(|(k, _)| *k == "origin")
1161 .map(|(_, v)| v)
1162 .unwrap_or_default();
1163 {
1164 let mut dom = it.dom.borrow_mut();
1165 let iframe_srcs: Vec<String> = dom
1166 .nodes
1167 .iter()
1168 .filter(|n| n.tag == "iframe")
1169 .filter_map(|n| n.attrs.get("src").cloned())
1170 .filter(|s| !s.is_empty())
1171 .collect();
1172 for src in iframe_srcs {
1173 dom.pending_child_messages.push((src, data.clone(), origin.clone()));
1174 }
1175 }
1176 deliver_window_message(it, data, &origin);
1177 Ok(Value::Undefined)
1178}
1179
1180pub(crate) fn parent_window_post_message(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1183 let data = structured_clone(it, Value::Undefined, a)?;
1184 let href = location_href(&it.global).unwrap_or_default();
1185 let origin = location_components(&href)
1186 .into_iter()
1187 .find(|(k, _)| *k == "origin")
1188 .map(|(_, v)| v)
1189 .unwrap_or_default();
1190 it.dom.borrow_mut().pending_parent_messages.push((data, origin));
1191 Ok(Value::Undefined)
1192}
1193
1194pub(crate) fn configure_frame_depth(global: &Rc<RefCell<Scope>>, depth: u32) {
1198 let win_opt = global.borrow().vars.get("window").cloned();
1199 let Some(Value::Object(win)) = win_opt else { return };
1200 if depth == 0 {
1201 let win_val = Value::Object(win.clone());
1202 win.borrow_mut().props.insert("parent".into(), win_val.clone());
1203 win.borrow_mut().props.insert("top".into(), win_val.clone());
1204 global.borrow_mut().vars.insert("parent".into(), win_val.clone());
1205 global.borrow_mut().vars.insert("top".into(), win_val);
1206 } else {
1207 let parent_win = Obj::plain();
1208 {
1209 let mut p = parent_win.borrow_mut();
1210 p.props.insert("_is_parent_window".into(), Value::Bool(true));
1211 p.props.insert(
1212 "postMessage".into(),
1213 nv("parent.postMessage", parent_window_post_message),
1214 );
1215 p.props.insert("frames".into(), Value::Object(Obj::array(alloc::vec![])));
1216 p.props.insert("length".into(), Value::Number(0.0));
1217 }
1218 let parent_val = Value::Object(parent_win.clone());
1219 parent_win.borrow_mut().props.insert("self".into(), parent_val.clone());
1220 parent_win.borrow_mut().props.insert("window".into(), parent_val.clone());
1221 parent_win.borrow_mut().props.insert("parent".into(), parent_val.clone());
1222 parent_win.borrow_mut().props.insert("top".into(), parent_val.clone());
1223 win.borrow_mut().props.insert("parent".into(), parent_val.clone());
1224 win.borrow_mut().props.insert("top".into(), parent_val.clone());
1225 global.borrow_mut().vars.insert("parent".into(), parent_val.clone());
1226 global.borrow_mut().vars.insert("top".into(), parent_val);
1227 }
1228}
1229
1230pub(crate) fn deliver_window_message(it: &mut Interp, data: Value, origin: &str) {
1232 let ev = Obj::plain();
1233 ev.borrow_mut().props.insert("type".into(), Value::str("message"));
1234 ev.borrow_mut().props.insert("data".into(), data);
1235 ev.borrow_mut().props.insert("origin".into(), Value::str(origin));
1236 ev.borrow_mut().props.insert("source".into(), Value::Null);
1237 let (listeners, onmessage, win_val) = match it.global.borrow().vars.get("window") {
1238 Some(Value::Object(w)) => {
1239 let b = w.borrow();
1240 (
1241 b.props.get("_message_listeners").cloned(),
1242 b.props.get("onmessage").cloned(),
1243 Value::Object(w.clone()),
1244 )
1245 }
1246 _ => (None, None, Value::Undefined),
1247 };
1248 if let Some(h) = onmessage.filter(|h| matches!(h, Value::Object(o) if o.borrow().is_callable())) {
1249 it.call_listener(&h, win_val, &[Value::Object(ev.clone())], "window.onmessage");
1250 }
1251 if let Some(Value::Object(arr)) = listeners {
1252 let entries: Vec<Value> = match &arr.borrow().kind {
1253 ObjKind::Array(items) => items.clone(),
1254 _ => Vec::new(),
1255 };
1256 for entry in entries {
1257 if window_listener_aborted(&entry) {
1258 continue;
1259 }
1260 let cb = event_target_listener_cb(&entry);
1261 it.call_listener(
1262 &cb,
1263 Value::Undefined,
1264 &[Value::Object(ev.clone())],
1265 "window listener",
1266 );
1267 }
1268 }
1269}
1270
1271pub(crate) fn report_error(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1278 let err = arg(a, 0);
1279 let message = if let Value::Object(o) = &err {
1280 o.borrow()
1281 .props
1282 .get("message")
1283 .map(|v| v.to_js_string())
1284 .unwrap_or_else(|| err.to_js_string())
1285 } else {
1286 err.to_js_string()
1287 };
1288 let _ = console_log(it, Value::Undefined, &[Value::str(&message)]);
1289 let ev = Obj::plain();
1290 {
1291 let mut e = ev.borrow_mut();
1292 e.props.insert("type".into(), Value::str("error"));
1293 e.props.insert("message".into(), Value::str(&message));
1294 e.props.insert("error".into(), err.clone());
1295 e.props.insert("filename".into(), Value::str(""));
1296 e.props.insert("lineno".into(), Value::Number(0.0));
1297 e.props.insert("colno".into(), Value::Number(0.0));
1298 }
1299 let window = match it.global.borrow().vars.get("window") {
1300 Some(Value::Object(w)) => Some(w.clone()),
1301 _ => None,
1302 };
1303 if let Some(w) = &window {
1304 let onerror = w.borrow().props.get("onerror").cloned();
1305 if let Some(f) = onerror.and_then(callable_or_none) {
1306 it.call_listener(
1307 &f,
1308 Value::Undefined,
1309 &[
1310 Value::str(&message),
1311 Value::str(""),
1312 Value::Number(0.0),
1313 Value::Number(0.0),
1314 err.clone(),
1315 ],
1316 "window.onerror",
1317 );
1318 }
1319 let listeners = w.borrow().props.get("_error_listeners").cloned();
1320 if let Some(Value::Object(arr)) = listeners {
1321 let entries: Vec<Value> = match &arr.borrow().kind {
1322 ObjKind::Array(items) => items.clone(),
1323 _ => Vec::new(),
1324 };
1325 for entry in entries {
1326 if window_listener_aborted(&entry) {
1327 continue;
1328 }
1329 let cb = event_target_listener_cb(&entry);
1330 it.call_listener(
1331 &cb,
1332 Value::Undefined,
1333 &[Value::Object(ev.clone())],
1334 "window listener",
1335 );
1336 }
1337 }
1338 }
1339 Ok(Value::Undefined)
1340}
1341
1342pub(crate) fn make_abort_error() -> Value {
1344 make_dom_exception("AbortError", "signal is aborted without reason")
1345}
1346pub(crate) fn dom_exception_ctor(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1349 let message = match arg(a, 0) {
1350 Value::Undefined => String::new(),
1351 v => v.to_js_string(),
1352 };
1353 let name = match arg(a, 1) {
1354 Value::Undefined => String::from("Error"),
1355 v => v.to_js_string(),
1356 };
1357 Ok(make_dom_exception(&name, &message))
1358}
1359pub(crate) fn dom_exception_legacy_code(name: &str) -> f64 {
1362 match name {
1363 "IndexSizeError" => 1.0,
1364 "HierarchyRequestError" => 3.0,
1365 "WrongDocumentError" => 4.0,
1366 "InvalidCharacterError" => 5.0,
1367 "NoModificationAllowedError" => 7.0,
1368 "NotFoundError" => 8.0,
1369 "NotSupportedError" => 9.0,
1370 "InUseAttributeError" => 10.0,
1371 "InvalidStateError" => 11.0,
1372 "SyntaxError" => 12.0,
1373 "InvalidModificationError" => 13.0,
1374 "NamespaceError" => 14.0,
1375 "InvalidAccessError" => 15.0,
1376 "SecurityError" => 18.0,
1377 "NetworkError" => 19.0,
1378 "AbortError" => 20.0,
1379 "TimeoutError" => 23.0,
1380 "DataCloneError" => 25.0,
1381 _ => 0.0,
1382 }
1383}
1384pub(crate) fn make_dom_exception(name: &str, message: &str) -> Value {
1388 let o = Obj::plain();
1389 {
1390 let mut b = o.borrow_mut();
1391 b.props.insert("name".into(), Value::str(name));
1392 b.props.insert("message".into(), Value::str(message));
1393 b.props
1394 .insert("code".into(), Value::Number(dom_exception_legacy_code(name)));
1395 b.props.insert("toString".into(), nv("toString", dom_exception_to_string));
1396 }
1397 Value::Object(o)
1398}
1399pub(crate) fn dom_exception_to_string(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1400 if let Value::Object(o) = &this {
1401 let b = o.borrow();
1402 let name = b.props.get("name").map(|v| v.to_js_string()).unwrap_or_default();
1403 let message = b.props.get("message").map(|v| v.to_js_string()).unwrap_or_default();
1404 return Ok(Value::str(if message.is_empty() {
1405 name
1406 } else {
1407 alloc::format!("{}: {}", name, message)
1408 }));
1409 }
1410 Ok(Value::str("DOMException"))
1411}
1412pub(crate) fn abort_controller_abort(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1418 if let Value::Object(o) = &this {
1419 let sig = o.borrow().props.get("signal").cloned();
1420 if let Some(Value::Object(s)) = sig {
1421 let already_aborted = matches!(s.borrow().props.get("aborted"), Some(Value::Bool(true)));
1422 if already_aborted {
1423 return Ok(Value::Undefined);
1424 }
1425 let reason = match arg(a, 0) {
1426 Value::Undefined => make_abort_error(),
1427 r => r,
1428 };
1429 {
1430 let mut b = s.borrow_mut();
1431 b.props.insert("aborted".into(), Value::Bool(true));
1432 b.props.insert("reason".into(), reason);
1433 }
1434 let cb = s.borrow().props.get("onabort").cloned();
1435 if let Some(f) = cb.and_then(callable_or_none) {
1436 it.call_listener(&f, Value::Object(s.clone()), &[], "AbortSignal onabort");
1437 }
1438 }
1439 }
1440 Ok(Value::Undefined)
1441}
1442pub(crate) fn abort_signal_add_event_listener(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1446 if let Value::Object(o) = &this {
1447 o.borrow_mut()
1448 .props
1449 .insert(format!("on{}", arg(a, 0).to_js_string()), arg(a, 1));
1450 }
1451 Ok(Value::Undefined)
1452}
1453pub(crate) fn abort_signal_remove_event_listener(_it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
1460 if let Value::Object(o) = &this {
1461 let key = format!("on{}", arg(a, 0).to_js_string());
1462 let target = arg(a, 1);
1463 let matches = match (o.borrow().props.get(&key), &target) {
1464 (Some(Value::Object(stored)), Value::Object(t)) => Rc::ptr_eq(stored, t),
1465 _ => false,
1466 };
1467 if matches {
1468 o.borrow_mut().props.shift_remove(&key);
1469 }
1470 }
1471 Ok(Value::Undefined)
1472}
1473pub(crate) fn abort_signal_illegal_ctor(it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
1475 Err(it.type_error("Illegal constructor"))
1476}
1477
1478pub(crate) fn abort_controller_ctor(_it: &mut Interp, _t: Value, _a: &[Value]) -> Result<Value, Value> {
1479 let signal = Obj::plain();
1480 {
1481 let mut s = signal.borrow_mut();
1482 s.props.insert("aborted".into(), Value::Bool(false));
1483 s.props.insert("reason".into(), Value::Undefined);
1484 s.props.insert(
1485 "addEventListener".into(),
1486 nv("addEventListener", abort_signal_add_event_listener),
1487 );
1488 s.props.insert(
1489 "removeEventListener".into(),
1490 nv("removeEventListener", abort_signal_remove_event_listener),
1491 );
1492 s.props.insert(
1493 "throwIfAborted".into(),
1494 nv("throwIfAborted", abort_signal_throw_if_aborted),
1495 );
1496 }
1497 let ctrl = Obj::plain();
1498 {
1499 let mut c = ctrl.borrow_mut();
1500 c.props.insert("signal".into(), Value::Object(signal));
1501 c.props
1502 .insert("abort".into(), nv("abort", abort_controller_abort));
1503 }
1504 Ok(Value::Object(ctrl))
1505}
1506pub(crate) fn abort_signal_throw_if_aborted(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
1512 if let Value::Object(o) = &this {
1513 let aborted = matches!(o.borrow().props.get("aborted"), Some(Value::Bool(true)));
1514 if aborted {
1515 let reason = o.borrow().props.get("reason").cloned().unwrap_or(Value::Undefined);
1516 return Err(reason);
1517 }
1518 }
1519 Ok(Value::Undefined)
1520}
1521pub(crate) fn abort_signal_abort_static(_it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1525 let signal = Obj::plain();
1526 {
1527 let mut s = signal.borrow_mut();
1528 s.props.insert("aborted".into(), Value::Bool(true));
1529 let reason = match arg(a, 0) {
1530 Value::Undefined => make_abort_error(),
1531 r => r,
1532 };
1533 s.props.insert("reason".into(), reason);
1534 s.props.insert(
1535 "addEventListener".into(),
1536 nv("addEventListener", abort_signal_add_event_listener),
1537 );
1538 s.props.insert(
1539 "removeEventListener".into(),
1540 nv("removeEventListener", abort_signal_remove_event_listener),
1541 );
1542 s.props.insert(
1543 "throwIfAborted".into(),
1544 nv("throwIfAborted", abort_signal_throw_if_aborted),
1545 );
1546 }
1547 Ok(Value::Object(signal))
1548}
1549pub(crate) fn abort_signal_timeout(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1563 let ms = arg(a, 0).to_number();
1564 let reason = make_dom_exception("TimeoutError", "signal timed out");
1565 it.global
1566 .borrow_mut()
1567 .vars
1568 .insert("__abort_signal_timeout_reason".into(), reason);
1569 let src = alloc::format!(
1570 "(function(){{ var c = new AbortController(); var r = __abort_signal_timeout_reason; \
1571 setTimeout(function(){{ c.abort(r); }}, {}); return c.signal; }})()",
1572 ms
1573 );
1574 let result = it.eval_source(&src);
1575 it.global.borrow_mut().vars.remove("__abort_signal_timeout_reason");
1576 result
1577}
1578pub(crate) fn abort_signal_any(it: &mut Interp, _t: Value, a: &[Value]) -> Result<Value, Value> {
1587 let signals = it.iter_to_vec(&arg(a, 0));
1588 it.global
1589 .borrow_mut()
1590 .vars
1591 .insert("__abort_signal_any_input".into(), Value::Object(Obj::array(signals)));
1592 let src = "(function(){ \
1593 var c = new AbortController(); \
1594 var arr = __abort_signal_any_input; \
1595 for (var i = 0; i < arr.length; i++) { \
1596 if (arr[i].aborted) { c.abort(arr[i].reason); return c.signal; } \
1597 } \
1598 for (var i = 0; i < arr.length; i++) { \
1599 (function(s){ \
1600 s.addEventListener('abort', function(){ if (!c.signal.aborted) c.abort(s.reason); }); \
1601 })(arr[i]); \
1602 } \
1603 return c.signal; \
1604 })()";
1605 let result = it.eval_source(src);
1606 it.global.borrow_mut().vars.remove("__abort_signal_any_input");
1607 result
1608}
1609