1use alloc::boxed::Box;
11use alloc::collections::{BTreeMap, VecDeque};
12use alloc::format;
13use alloc::rc::Rc;
14use alloc::string::String;
15use alloc::string::ToString;
16use alloc::vec::Vec;
17use core::cell::RefCell;
18
19use super::ast::*;
20use super::dom_bridge::DomBridge;
21use super::lexer::Lexer;
22use super::parser::Parser;
23use super::value::*;
24
25const DEFAULT_MAX_STEPS: u64 = 5_000_000;
26const DEFAULT_MAX_DEPTH: u32 = 400;
27
28const MODULE_EXPORTS_KEY: &str = "\u{0}module.exports";
31
32pub struct ModuleRecord {
36 pub source: String,
37 pub exports: Option<Rc<RefCell<BTreeMap<String, Value>>>>,
38}
39
40pub type ModuleRegistry = Rc<RefCell<BTreeMap<String, ModuleRecord>>>;
42
43pub struct Scope {
45 pub vars: BTreeMap<String, Value>,
46 pub parent: Option<Rc<RefCell<Scope>>>,
47}
48
49impl Scope {
50 pub fn new_root() -> Rc<RefCell<Scope>> {
51 Rc::new(RefCell::new(Scope {
52 vars: BTreeMap::new(),
53 parent: None,
54 }))
55 }
56 pub fn child(parent: Rc<RefCell<Scope>>) -> Rc<RefCell<Scope>> {
57 Rc::new(RefCell::new(Scope {
58 vars: BTreeMap::new(),
59 parent: Some(parent),
60 }))
61 }
62}
63
64pub(crate) fn scope_get(scope: &Rc<RefCell<Scope>>, name: &str) -> Option<Value> {
65 let b = scope.borrow();
66 if let Some(v) = b.vars.get(name) {
67 return Some(v.clone());
68 }
69 match &b.parent {
70 Some(p) => scope_get(p, name),
71 None => None,
72 }
73}
74
75fn scope_assign(scope: &Rc<RefCell<Scope>>, name: &str, val: Value) -> bool {
77 {
78 let mut b = scope.borrow_mut();
79 if b.vars.contains_key(name) {
80 b.vars.insert(name.to_string(), val);
81 return true;
82 }
83 }
84 let parent = scope.borrow().parent.clone();
85 match parent {
86 Some(p) => scope_assign(&p, name, val),
87 None => false,
88 }
89}
90
91fn scope_declare(scope: &Rc<RefCell<Scope>>, name: &str, val: Value) {
92 scope.borrow_mut().vars.insert(name.to_string(), val);
93}
94
95fn infer_function_name(v: &Value, name: &str) {
100 if let Value::Object(o) = v {
101 if let ObjKind::Function(fd) = &mut o.borrow_mut().kind {
102 if fd.name.is_empty() {
103 fd.name = String::from(name);
104 }
105 }
106 }
107}
108
109pub(crate) enum Completion {
111 Normal(Value),
112 Return(Value),
113 Break(Option<String>),
114 Continue(Option<String>),
115}
116
117struct GenReplay {
122 sent: Vec<super::value::GenCompletion>,
124 counter: usize,
126 target: usize,
128 suspending: bool,
130 yielded: Value,
132 returning: Option<Value>,
134}
135
136pub struct JsRuntime {
138 pub global: Rc<RefCell<Scope>>,
139 pub out: String,
141 pub dom: Rc<RefCell<DomBridge>>,
143 pub microtasks: Rc<RefCell<VecDeque<Job>>>,
145 pub macrotasks: Rc<RefCell<VecDeque<(Value, Vec<Value>, u64)>>>,
147 pub base_url: String,
149 pub modules: ModuleRegistry,
151 pub last_syntax_errors: Vec<super::parser::ParseError>,
158}
159
160impl Default for JsRuntime {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166impl JsRuntime {
167 pub fn new() -> Self {
168 let global = Scope::new_root();
169 super::builtins::install(&global);
170 JsRuntime {
171 global,
172 out: String::new(),
173 dom: Rc::new(RefCell::new(DomBridge::new())),
174 microtasks: Rc::new(RefCell::new(VecDeque::new())),
175 macrotasks: Rc::new(RefCell::new(VecDeque::new())),
176 base_url: String::new(),
177 modules: Rc::new(RefCell::new(BTreeMap::new())),
178 last_syntax_errors: Vec::new(),
179 }
180 }
181
182 fn new_interp(&self) -> Interp {
183 let base_url = super::builtins::location_href(&self.global)
186 .filter(|s| !s.is_empty())
187 .unwrap_or_else(|| self.base_url.clone());
188 Interp {
189 steps: 0,
190 max_steps: DEFAULT_MAX_STEPS,
191 depth: 0,
192 max_depth: DEFAULT_MAX_DEPTH,
193 aborted: false,
194 out: String::new(),
195 dom: self.dom.clone(),
196 microtasks: self.microtasks.clone(),
197 gen_replay: None,
198 macrotasks: self.macrotasks.clone(),
199 base_url,
200 global: self.global.clone(),
201 io_callbacks: Vec::new(),
202 intervals: Vec::new(),
203 modules: self.modules.clone(),
204 pending_label: None,
205 pending_new_target: None,
206 new_target_stack: Vec::new(),
207 }
208 }
209
210 pub fn set_page_url(&mut self, url: &str) {
213 self.base_url = String::from(url);
214 super::builtins::update_location(&self.global, url);
215 }
216
217 pub fn define_module(&mut self, specifier: &str, source: &str) {
220 self.modules.borrow_mut().insert(
221 String::from(specifier),
222 ModuleRecord {
223 source: String::from(source),
224 exports: None,
225 },
226 );
227 }
228
229 pub fn eval(&mut self, source: &str) -> Result<Value, String> {
235 let mut parser = Parser::new(Lexer::new(source));
236 let program = parser.parse_program();
237 let total_errors = parser.error_count();
238 self.last_syntax_errors = parser.take_errors();
239 if total_errors > 0 {
240 crate::warn!(
241 "[JS] {} syntax error(s) recovered; executing best-effort",
242 total_errors
243 );
244 for e in self.last_syntax_errors.iter().take(5) {
245 crate::warn!(
246 "[JS] SyntaxError: {} (token #{})",
247 e.message,
248 e.token_index
249 );
250 }
251 }
252 let mut interp = self.new_interp();
253 let scope = self.global.clone();
254 let result = interp.exec_statements(&program.body, &scope, &Value::Undefined);
255 interp.run_event_loop();
257 super::builtins::fire_intersection_observers(&mut interp);
259 super::builtins::flush_mutation_observers(&mut interp);
261 self.out.push_str(&interp.out);
263 if interp.aborted {
264 return Err(String::from("script aborted (step/depth budget exceeded)"));
265 }
266 match result {
267 Ok(Completion::Normal(v)) | Ok(Completion::Return(v)) => Ok(v),
268 Ok(_) => Ok(Value::Undefined),
269 Err(thrown) => {
270 let err_str = thrown.to_js_string();
271 if err_str == "[object Object]" {
272 if let Value::Object(o) = &thrown {
273 let name = o.borrow().props.get("name").map(|v| v.to_js_string()).unwrap_or_else(|| alloc::string::String::from("Error"));
274 let msg = o.borrow().props.get("message").map(|v| v.to_js_string()).unwrap_or_else(|| alloc::string::String::from(""));
275 let stack = o.borrow().props.get("stack").map(|v| v.to_js_string()).unwrap_or_else(|| alloc::string::String::from(""));
276 crate::println!("[JS_ERR] {}: {}\nStack:\n{}", name, msg, stack);
277 }
278 }
279 Err(err_str)
280 }
281 }
282 }
283
284 pub fn dispatch_click(&mut self, node_idx: usize) -> bool {
287 self.dispatch_event_with(node_idx, "click", &[]).0
288 }
289
290 pub fn dispatch_event(&mut self, node_idx: usize, event_type: &str) -> bool {
292 if matches!(event_type, "input" | "change") {
300 self.dom.borrow_mut().set_attr(node_idx, "_user_interacted", "1");
301 }
302 self.dispatch_event_with(node_idx, event_type, &[]).0
303 }
304
305 pub fn dispatch_mouse(
308 &mut self,
309 node_idx: usize,
310 event_type: &str,
311 x: i32,
312 y: i32,
313 ) -> (bool, bool) {
314 self.dispatch_mouse_full(node_idx, event_type, x, y, 0, 0)
315 }
316
317 pub fn dispatch_mouse_full(
322 &mut self,
323 node_idx: usize,
324 event_type: &str,
325 x: i32,
326 y: i32,
327 button: i32,
328 wheel_delta: i32,
329 ) -> (bool, bool) {
330 let buttons = match event_type {
332 "mousedown" => match button {
333 0 => 1,
334 2 => 2,
335 1 => 4,
336 _ => 0,
337 },
338 _ => 0,
339 };
340 let detail = match event_type {
342 "click" | "mousedown" | "mouseup" => 1.0,
343 "dblclick" => 2.0,
344 _ => 0.0,
345 };
346 let (ox, oy) = (x, y);
349 let mut extra = alloc::vec![
350 (String::from("clientX"), Value::Number(x as f64)),
351 (String::from("clientY"), Value::Number(y as f64)),
352 (String::from("pageX"), Value::Number(x as f64)),
353 (String::from("pageY"), Value::Number(y as f64)),
354 (String::from("screenX"), Value::Number(x as f64)),
355 (String::from("screenY"), Value::Number(y as f64)),
356 (String::from("offsetX"), Value::Number(ox as f64)),
357 (String::from("offsetY"), Value::Number(oy as f64)),
358 (String::from("movementX"), Value::Number(0.0)),
359 (String::from("movementY"), Value::Number(0.0)),
360 (String::from("button"), Value::Number(button as f64)),
361 (String::from("buttons"), Value::Number(buttons as f64)),
362 (String::from("detail"), Value::Number(detail)),
363 ];
364 if event_type == "wheel" {
366 extra.push((String::from("deltaX"), Value::Number(0.0)));
367 extra.push((
368 String::from("deltaY"),
369 Value::Number((wheel_delta * 100) as f64),
370 ));
371 extra.push((String::from("deltaZ"), Value::Number(0.0)));
372 extra.push((String::from("deltaMode"), Value::Number(0.0))); }
374 Self::push_modifier_props(&mut extra);
375 self.dispatch_event_with(node_idx, event_type, &extra)
376 }
377
378 fn push_modifier_props(extra: &mut Vec<(String, Value)>) {
380 extra.push((
381 String::from("shiftKey"),
382 Value::Bool(crate::kernel::keyboard::shift_down()),
383 ));
384 extra.push((
385 String::from("ctrlKey"),
386 Value::Bool(crate::kernel::keyboard::ctrl_down()),
387 ));
388 extra.push((
389 String::from("altKey"),
390 Value::Bool(crate::kernel::keyboard::alt_down()),
391 ));
392 extra.push((
393 String::from("metaKey"),
394 Value::Bool(crate::kernel::keyboard::meta_down()),
395 ));
396 }
397
398 pub fn dispatch_key(&mut self, node_idx: usize, event_type: &str, key: &str) -> (bool, bool) {
402 self.dispatch_key_full(node_idx, event_type, key, false)
403 }
404
405 pub fn dispatch_key_full(
408 &mut self,
409 node_idx: usize,
410 event_type: &str,
411 key: &str,
412 repeat: bool,
413 ) -> (bool, bool) {
414 let (key_code, code, location) = Self::key_attributes(key);
415 let mut extra = alloc::vec![
416 (String::from("key"), Value::str(key)),
417 (String::from("keyCode"), Value::Number(key_code as f64)),
418 (String::from("which"), Value::Number(key_code as f64)),
419 (
420 String::from("charCode"),
421 Value::Number(if event_type == "keypress" {
422 key_code as f64
423 } else {
424 0.0
425 })
426 ),
427 (String::from("code"), Value::str(&code)),
428 (String::from("location"), Value::Number(location as f64)),
429 (String::from("repeat"), Value::Bool(repeat)),
430 ];
431 Self::push_modifier_props(&mut extra);
432 self.dispatch_event_with(node_idx, event_type, &extra)
433 }
434
435 fn key_attributes(key: &str) -> (u32, String, u32) {
438 match key {
440 "Enter" => (13, String::from("Enter"), 0),
441 "Tab" => (9, String::from("Tab"), 0),
442 "Backspace" => (8, String::from("Backspace"), 0),
443 "Escape" => (27, String::from("Escape"), 0),
444 " " => (32, String::from("Space"), 0),
445 "Delete" => (46, String::from("Delete"), 0),
446 "ArrowLeft" => (37, String::from("ArrowLeft"), 0),
447 "ArrowUp" => (38, String::from("ArrowUp"), 0),
448 "ArrowRight" => (39, String::from("ArrowRight"), 0),
449 "ArrowDown" => (40, String::from("ArrowDown"), 0),
450 "Home" => (36, String::from("Home"), 0),
451 "End" => (35, String::from("End"), 0),
452 "PageUp" => (33, String::from("PageUp"), 0),
453 "PageDown" => (34, String::from("PageDown"), 0),
454 "Shift" => (16, String::from("ShiftLeft"), 1),
455 "Control" => (17, String::from("ControlLeft"), 1),
456 "Alt" => (18, String::from("AltLeft"), 1),
457 "Meta" => (91, String::from("MetaLeft"), 1),
458 _ => {
459 let mut chars = key.chars();
461 if let (Some(c), None) = (chars.next(), chars.clone().next()) {
462 let upper = c.to_ascii_uppercase();
463 if upper.is_ascii_alphabetic() {
464 let code = alloc::format!("Key{}", upper);
465 return (upper as u32, code, 0);
466 }
467 if c.is_ascii_digit() {
468 let code = alloc::format!("Digit{}", c);
469 return (c as u32, code, 0);
470 }
471 return (c as u32, String::new(), 0);
472 }
473 (0, String::new(), 0)
474 }
475 }
476 }
477
478 pub fn dispatch_event_with(
484 &mut self,
485 node_idx: usize,
486 event_type: &str,
487 extra: &[(String, Value)],
488 ) -> (bool, bool) {
489 let path = {
491 let dom = self.dom.borrow();
492 let mut path = alloc::vec![node_idx];
493 let mut cur = node_idx;
494 let mut guard = 0;
495 while let Some(p) = dom.nodes.get(cur).and_then(|n| n.parent) {
496 path.push(p);
497 cur = p;
498 guard += 1;
499 if guard > dom.nodes.len() {
500 break;
501 }
502 }
503 path
504 };
505 let any = path
506 .iter()
507 .any(|&n| self.dom.borrow().has_listener_on(n, event_type));
508 if !any {
509 return (false, false);
510 }
511
512 let bubbles = extra
518 .iter()
519 .find(|(k, _)| k == "bubbles")
520 .map(|(_, v)| v.truthy())
521 .unwrap_or(true);
522 let composed: Vec<Value> = path.iter().map(|&n| Value::Object(Obj::dom(n))).collect();
524
525 let target = Value::Object(Obj::dom(node_idx));
527 let ev = Obj::plain();
528 {
529 let mut e = ev.borrow_mut();
530 e.props.insert(String::from("type"), Value::str(event_type));
531 e.props.insert(String::from("target"), target.clone());
532 e.props.insert(String::from("bubbles"), Value::Bool(true));
533 e.props.insert(String::from("cancelable"), Value::Bool(true));
539 e.props.insert(String::from("isTrusted"), Value::Bool(false));
545 e.props
546 .insert(String::from("eventPhase"), Value::Number(0.0));
547 e.props
548 .insert(String::from("defaultPrevented"), Value::Bool(false));
549 e.props.insert(
552 String::from("timeStamp"),
553 Value::Number(super::builtins::next_perf_timestamp()),
554 );
555 e.props.insert(String::from("returnValue"), Value::Bool(true));
558 e.props.insert(String::from("cancelBubble"), Value::Bool(false));
559 e.props.insert(String::from("srcElement"), target.clone());
560 e.props.insert(
561 String::from("_composedPath"),
562 Value::Object(Obj::array(composed)),
563 );
564 for (k, v) in extra {
565 e.props.insert(k.clone(), v.clone());
566 }
567 e.props.insert(
568 String::from("preventDefault"),
569 Value::Object(Obj::native("preventDefault", |_, this, _| {
570 if let Value::Object(o) = &this {
571 let cancelable = o
572 .borrow()
573 .props
574 .get("cancelable")
575 .map(|v| v.truthy())
576 .unwrap_or(true);
577 if cancelable {
578 let mut b = o.borrow_mut();
579 b.props
580 .insert(String::from("defaultPrevented"), Value::Bool(true));
581 b.props.insert(String::from("returnValue"), Value::Bool(false));
585 }
586 }
587 Ok(Value::Undefined)
588 })),
589 );
590 e.props.insert(
591 String::from("stopPropagation"),
592 Value::Object(Obj::native("stopPropagation", |_, this, _| {
593 if let Value::Object(o) = &this {
594 let mut b = o.borrow_mut();
595 b.props
596 .insert(String::from("_stop"), Value::Bool(true));
597 b.props.insert(String::from("cancelBubble"), Value::Bool(true));
600 }
601 Ok(Value::Undefined)
602 })),
603 );
604 e.props.insert(
605 String::from("stopImmediatePropagation"),
606 Value::Object(Obj::native("stopImmediatePropagation", |_, this, _| {
607 if let Value::Object(o) = &this {
608 let mut b = o.borrow_mut();
609 b.props.insert(String::from("_stop"), Value::Bool(true));
610 b.props
611 .insert(String::from("_stopImmediate"), Value::Bool(true));
612 b.props.insert(String::from("cancelBubble"), Value::Bool(true));
613 }
614 Ok(Value::Undefined)
615 })),
616 );
617 e.props.insert(
618 String::from("composedPath"),
619 Value::Object(Obj::native("composedPath", |_, this, _| {
620 if let Value::Object(o) = &this {
621 if let Some(p) = o.borrow().props.get("_composedPath") {
622 return Ok(p.clone());
623 }
624 }
625 Ok(Value::Object(Obj::array(Vec::new())))
626 })),
627 );
628 }
629
630 let mut steps: Vec<(usize, bool, f64)> = Vec::new();
634 for &n in path.iter().skip(1).rev() {
636 steps.push((n, true, 1.0));
637 }
638 steps.push((node_idx, true, 2.0));
640 steps.push((node_idx, false, 2.0));
641 if bubbles {
643 for &n in path.iter().skip(1) {
644 steps.push((n, false, 3.0));
645 }
646 }
647
648 let mut fired = false;
649 let mut to_remove: Vec<u64> = Vec::new();
650 'outer: for (n, want_capture, phase) in steps {
651 let listeners = self
652 .dom
653 .borrow()
654 .listeners_phase(n, event_type, want_capture);
655 if listeners.is_empty() {
656 continue;
657 }
658 {
659 let mut e = ev.borrow_mut();
660 e.props
661 .insert(String::from("currentTarget"), Value::Object(Obj::dom(n)));
662 e.props
663 .insert(String::from("eventPhase"), Value::Number(phase));
664 }
665 let node_this = Value::Object(Obj::dom(n));
666 for (func, once, id) in listeners {
667 let mut interp = self.new_interp();
668 let ctx = alloc::format!("{} listener", event_type);
669 interp.call_listener(
670 &func,
671 node_this.clone(),
672 &[Value::Object(ev.clone())],
673 &ctx,
674 );
675 interp.run_event_loop();
676 self.out.push_str(&interp.out);
677 fired = true;
678 if once {
679 to_remove.push(id);
680 }
681 if ev
682 .borrow()
683 .props
684 .get("_stopImmediate")
685 .map(|v| v.truthy())
686 .unwrap_or(false)
687 {
688 break 'outer;
689 }
690 }
691 if ev
692 .borrow()
693 .props
694 .get("_stop")
695 .map(|v| v.truthy())
696 .unwrap_or(false)
697 {
698 break 'outer;
699 }
700 }
701 if !to_remove.is_empty() {
703 let mut dom = self.dom.borrow_mut();
704 for id in to_remove {
705 dom.remove_listener_by_id(id);
706 }
707 }
708 let default_prevented = ev
709 .borrow()
710 .props
711 .get("defaultPrevented")
712 .map(|v| v.truthy())
713 .unwrap_or(false);
714 (fired, default_prevented)
715 }
716
717 pub fn take_pending_reset(&mut self) -> Option<usize> {
720 let g = self.global.clone();
721 let mut gb = g.borrow_mut();
722 let v = match gb.vars.get("__pending_reset") {
723 Some(Value::Number(n)) if *n >= 1.0 => *n as usize - 1,
724 _ => return None,
725 };
726 gb.vars.insert("__pending_reset".into(), Value::Number(0.0));
727 Some(v)
728 }
729
730 pub fn take_pending_nav(&mut self) -> i64 {
733 let history = match self.global.borrow().vars.get("history") {
734 Some(Value::Object(h)) => h.clone(),
735 _ => return 0,
736 };
737 let mut hb = history.borrow_mut();
738 let n = match hb.props.get("_pending_nav") {
739 Some(Value::Number(n)) => *n as i64,
740 _ => 0,
741 };
742 if n != 0 {
743 hb.props.insert("_pending_nav".into(), Value::Number(0.0));
744 }
745 n
746 }
747
748 pub fn take_pending_location(&mut self) -> Option<(String, String)> {
753 let location = match self.global.borrow().vars.get("location") {
754 Some(Value::Object(l)) => l.clone(),
755 _ => return None,
756 };
757 let mut lb = location.borrow_mut();
758 let url = match lb.props.get("_pending_location") {
759 Some(v) => v.to_js_string(),
760 _ => return None,
761 };
762 let mode = lb
763 .props
764 .get("_pending_location_mode")
765 .map(|v| v.to_js_string())
766 .unwrap_or_else(|| String::from("assign"));
767 lb.props.shift_remove("_pending_location");
768 lb.props.shift_remove("_pending_location_mode");
769 Some((url, mode))
770 }
771
772 fn window_listeners(&self, key: &str) -> Vec<Value> {
779 let window = match self.global.borrow().vars.get("window") {
780 Some(Value::Object(w)) => w.clone(),
781 _ => return Vec::new(),
782 };
783 let entries = match window.borrow().props.get(key) {
784 Some(Value::Object(arr)) => match &arr.borrow().kind {
785 ObjKind::Array(items) => items.clone(),
786 _ => Vec::new(),
787 },
788 _ => Vec::new(),
789 };
790 entries
791 .iter()
792 .filter_map(|entry| {
793 let Value::Object(o) = entry else {
794 return Some(entry.clone());
795 };
796 let ObjKind::Array(items) = &o.borrow().kind else {
797 return Some(entry.clone());
798 };
799 if items.get(1).is_some_and(super::dom_bridge::is_signal_aborted) {
800 return None;
801 }
802 items.first().cloned()
803 })
804 .collect()
805 }
806
807 fn fire_window_listeners(
809 &mut self,
810 listeners: &[Value],
811 event_type: &str,
812 extra: &[(String, Value)],
813 ) {
814 if listeners.is_empty() {
815 return;
816 }
817 let ev = Obj::plain();
818 {
819 let mut e = ev.borrow_mut();
820 e.props.insert(String::from("type"), Value::str(event_type));
821 for (k, v) in extra {
822 e.props.insert(k.clone(), v.clone());
823 }
824 }
825 for cb in listeners {
826 let mut interp = self.new_interp();
827 let ctx = alloc::format!("{} listener (window)", event_type);
828 interp.call_listener(cb, Value::Undefined, &[Value::Object(ev.clone())], &ctx);
829 interp.run_event_loop();
830 self.out.push_str(&interp.out);
831 }
832 }
833
834 pub fn fire_popstate(&mut self) {
837 let listeners = self.window_listeners("_popstate_listeners");
838 let state = match self.global.borrow().vars.get("history") {
840 Some(Value::Object(h)) => h
841 .borrow()
842 .props
843 .get("state")
844 .cloned()
845 .unwrap_or(Value::Null),
846 _ => Value::Null,
847 };
848 if !listeners.is_empty() {
849 self.fire_window_listeners(&listeners, "popstate", &[(String::from("state"), state)]);
850 }
851 }
852
853 pub fn has_spa_back(&self) -> bool {
855 let history = match self.global.borrow().vars.get("history") {
856 Some(Value::Object(h)) => h.clone(),
857 _ => return false,
858 };
859 let history_borrow = history.borrow();
860 match history_borrow.props.get("_spa_back_stack") {
861 Some(Value::Object(arr)) => {
862 let arr_borrow = arr.borrow();
863 match &arr_borrow.kind {
864 ObjKind::Array(items) => !items.is_empty(),
865 _ => false,
866 }
867 }
868 _ => false,
869 }
870 }
871
872 pub fn spa_go_back(&mut self) -> Option<String> {
875 let history = match self.global.borrow().vars.get("history") {
876 Some(Value::Object(h)) => h.clone(),
877 _ => return None,
878 };
879 {
881 let cur_url = super::builtins::location_href(&self.global).unwrap_or_default();
882 let cur_state = history.borrow().props.get("state").cloned().unwrap_or(Value::Null);
883 let fwd_stack_val = history.borrow().props.get("_spa_fwd_stack").cloned();
884 let fwd_stack = match fwd_stack_val {
885 Some(Value::Object(arr)) => arr,
886 _ => Obj::array(alloc::vec![]),
887 };
888 let entry = Obj::plain();
889 {
890 let mut eb = entry.borrow_mut();
891 eb.props.insert("url".into(), Value::str(cur_url));
892 eb.props.insert("state".into(), cur_state);
893 }
894 if let ObjKind::Array(items) = &mut fwd_stack.borrow_mut().kind {
895 items.push(Value::Object(entry));
896 }
897 history.borrow_mut().props.insert("_spa_fwd_stack".into(), Value::Object(fwd_stack));
898 }
899 let (prev_url, prev_state) = {
901 let back_stack_val = history.borrow().props.get("_spa_back_stack").cloned();
902 let back_stack = match back_stack_val {
903 Some(Value::Object(arr)) => arr,
904 _ => return None,
905 };
906 let entry = match &mut back_stack.borrow_mut().kind {
907 ObjKind::Array(items) => items.pop(),
908 _ => None,
909 }?;
910 let eb = match entry { Value::Object(o) => o, _ => return None };
911 let url = eb.borrow().props.get("url").cloned().unwrap_or(Value::Undefined).to_js_string();
912 let state = eb.borrow().props.get("state").cloned().unwrap_or(Value::Null);
913 (url, state)
914 };
915 history.borrow_mut().props.insert("state".into(), prev_state);
917 super::builtins::update_location(&self.global, &prev_url);
918 self.base_url = prev_url.to_string();
919 self.fire_popstate();
920 Some(prev_url.to_string())
921 }
922
923 pub fn spa_go_forward(&mut self) -> Option<String> {
925 let history = match self.global.borrow().vars.get("history") {
926 Some(Value::Object(h)) => h.clone(),
927 _ => return None,
928 };
929 {
931 let cur_url = super::builtins::location_href(&self.global).unwrap_or_default();
932 let cur_state = history.borrow().props.get("state").cloned().unwrap_or(Value::Null);
933 let back_stack_val = history.borrow().props.get("_spa_back_stack").cloned();
934 let back_stack = match back_stack_val {
935 Some(Value::Object(arr)) => arr,
936 _ => Obj::array(alloc::vec![]),
937 };
938 let entry = Obj::plain();
939 {
940 let mut eb = entry.borrow_mut();
941 eb.props.insert("url".into(), Value::str(cur_url));
942 eb.props.insert("state".into(), cur_state);
943 }
944 if let ObjKind::Array(items) = &mut back_stack.borrow_mut().kind {
945 items.push(Value::Object(entry));
946 }
947 history.borrow_mut().props.insert("_spa_back_stack".into(), Value::Object(back_stack));
948 }
949 let (next_url, next_state) = {
951 let fwd_stack_val = history.borrow().props.get("_spa_fwd_stack").cloned();
952 let fwd_stack = match fwd_stack_val {
953 Some(Value::Object(arr)) => arr,
954 _ => return None,
955 };
956 let entry = match &mut fwd_stack.borrow_mut().kind {
957 ObjKind::Array(items) => items.pop(),
958 _ => None,
959 }?;
960 let eb = match entry { Value::Object(o) => o, _ => return None };
961 let url = eb.borrow().props.get("url").cloned().unwrap_or(Value::Undefined).to_js_string();
962 let state = eb.borrow().props.get("state").cloned().unwrap_or(Value::Null);
963 (url, state)
964 };
965 history.borrow_mut().props.insert("state".into(), next_state);
966 super::builtins::update_location(&self.global, &next_url);
967 self.base_url = next_url.to_string();
968 self.fire_popstate();
969 Some(next_url.to_string())
970 }
971
972 pub fn has_spa_forward(&self) -> bool {
974 let history = match self.global.borrow().vars.get("history") {
975 Some(Value::Object(h)) => h.clone(),
976 _ => return false,
977 };
978 let history_borrow = history.borrow();
979 match history_borrow.props.get("_spa_fwd_stack") {
980 Some(Value::Object(arr)) => {
981 let arr_borrow = arr.borrow();
982 match &arr_borrow.kind {
983 ObjKind::Array(items) => !items.is_empty(),
984 _ => false,
985 }
986 }
987 _ => false,
988 }
989 }
990
991 pub fn fire_scroll(&mut self, scroll_y: i32) {
994 {
997 let g = self.global.borrow();
998 if let Some(Value::Object(w)) = g.vars.get("window") {
999 let mut wb = w.borrow_mut();
1000 wb.props
1001 .insert("scrollY".into(), Value::Number(scroll_y as f64));
1002 wb.props
1003 .insert("pageYOffset".into(), Value::Number(scroll_y as f64));
1004 }
1005 }
1006 {
1007 let mut g = self.global.borrow_mut();
1008 g.vars
1009 .insert("scrollY".into(), Value::Number(scroll_y as f64));
1010 g.vars
1011 .insert("pageYOffset".into(), Value::Number(scroll_y as f64));
1012 }
1013 let listeners = self.window_listeners("_scroll_listeners");
1014 self.fire_window_listeners(
1015 &listeners,
1016 "scroll",
1017 &[(String::from("scrollY"), Value::Number(scroll_y as f64))],
1018 );
1019 }
1020
1021 pub fn fire_hashchange(&mut self, old_url: &str, new_url: &str) {
1024 let new_hash = match new_url.split_once('#') {
1026 Some((_, frag)) => alloc::format!("#{}", frag),
1027 None => String::new(),
1028 };
1029 {
1030 let g = self.global.borrow();
1031 if let Some(Value::Object(loc)) = g.vars.get("location") {
1032 loc.borrow_mut()
1033 .props
1034 .insert("hash".into(), Value::str(&new_hash));
1035 }
1036 }
1037 let listeners = self.window_listeners("_hashchange_listeners");
1038 self.fire_window_listeners(
1039 &listeners,
1040 "hashchange",
1041 &[
1042 (String::from("oldURL"), Value::str(old_url)),
1043 (String::from("newURL"), Value::str(new_url)),
1044 ],
1045 );
1046 }
1047
1048 pub fn fire_resize(&mut self, width: i32, height: i32) {
1051 {
1052 let g = self.global.borrow();
1053 if let Some(Value::Object(w)) = g.vars.get("window") {
1054 let mut wb = w.borrow_mut();
1055 wb.props
1056 .insert("innerWidth".into(), Value::Number(width as f64));
1057 wb.props
1058 .insert("innerHeight".into(), Value::Number(height as f64));
1059 }
1060 }
1061 {
1062 let mut g = self.global.borrow_mut();
1064 g.vars
1065 .insert("innerWidth".into(), Value::Number(width as f64));
1066 g.vars
1067 .insert("innerHeight".into(), Value::Number(height as f64));
1068 }
1069 let listeners = self.window_listeners("_resize_listeners");
1070 self.fire_window_listeners(
1071 &listeners,
1072 "resize",
1073 &[
1074 (String::from("innerWidth"), Value::Number(width as f64)),
1075 (String::from("innerHeight"), Value::Number(height as f64)),
1076 ],
1077 );
1078 }
1079}
1080
1081pub struct Interp {
1083 pub steps: u64,
1084 pub max_steps: u64,
1085 pub depth: u32,
1086 pub max_depth: u32,
1087 pub aborted: bool,
1088 pub out: String,
1089 pub dom: Rc<RefCell<DomBridge>>,
1091 pub microtasks: Rc<RefCell<VecDeque<Job>>>,
1093 gen_replay: Option<GenReplay>,
1095 pub macrotasks: Rc<RefCell<VecDeque<(Value, Vec<Value>, u64)>>>,
1097 pub base_url: String,
1099 pub global: Rc<RefCell<Scope>>,
1101 pub io_callbacks: Vec<Value>,
1103 pub intervals: Vec<(u64, Value, Vec<Value>, u32)>,
1105 pub modules: ModuleRegistry,
1107 pending_label: Option<String>,
1112 pending_new_target: Option<Value>,
1116 new_target_stack: Vec<Value>,
1119}
1120
1121type EvalResult = Result<Value, Value>;
1122
1123
1124mod exec;
1129mod iter_gen;
1130mod eval_expr;
1131mod promise_loop;
1132mod operators;
1133mod calls;
1134mod properties;
1135mod dom_props;
1136
1137
1138fn aria_attr_name(prop: &str) -> Option<&'static str> {
1144 Some(match prop {
1145 "role" => "role",
1146 "ariaLabel" => "aria-label",
1147 "ariaLabelledBy" => "aria-labelledby",
1148 "ariaDescribedBy" => "aria-describedby",
1149 "ariaHidden" => "aria-hidden",
1150 "ariaExpanded" => "aria-expanded",
1151 "ariaChecked" => "aria-checked",
1152 "ariaSelected" => "aria-selected",
1153 "ariaDisabled" => "aria-disabled",
1154 "ariaPressed" => "aria-pressed",
1155 "ariaCurrent" => "aria-current",
1156 "ariaLive" => "aria-live",
1157 "ariaBusy" => "aria-busy",
1158 "ariaRequired" => "aria-required",
1159 "ariaInvalid" => "aria-invalid",
1160 "ariaValueNow" => "aria-valuenow",
1161 "ariaValueMin" => "aria-valuemin",
1162 "ariaValueMax" => "aria-valuemax",
1163 "ariaValueText" => "aria-valuetext",
1164 "ariaControls" => "aria-controls",
1165 "ariaOwns" => "aria-owns",
1166 "ariaModal" => "aria-modal",
1167 "ariaMultiline" => "aria-multiline",
1168 "ariaMultiSelectable" => "aria-multiselectable",
1169 "ariaOrientation" => "aria-orientation",
1170 "ariaPlaceholder" => "aria-placeholder",
1171 "ariaReadOnly" => "aria-readonly",
1172 "ariaRoleDescription" => "aria-roledescription",
1173 "ariaSort" => "aria-sort",
1174 "ariaAtomic" => "aria-atomic",
1175 "ariaHasPopup" => "aria-haspopup",
1176 "ariaColCount" => "aria-colcount",
1179 "ariaColIndex" => "aria-colindex",
1180 "ariaColSpan" => "aria-colspan",
1181 "ariaRowCount" => "aria-rowcount",
1182 "ariaRowIndex" => "aria-rowindex",
1183 "ariaRowSpan" => "aria-rowspan",
1184 "ariaSetSize" => "aria-setsize",
1185 "ariaPosInSet" => "aria-posinset",
1186 "ariaLevel" => "aria-level",
1187 "ariaKeyShortcuts" => "aria-keyshortcuts",
1188 "ariaAutoComplete" => "aria-autocomplete",
1189 "ariaDetails" => "aria-details",
1190 "ariaErrorMessage" => "aria-errormessage",
1191 "ariaFlowTo" => "aria-flowto",
1192 "ariaRelevant" => "aria-relevant",
1193 _ => return None,
1194 })
1195}
1196
1197fn camel_to_kebab(key: &str) -> String {
1200 let mut s = String::new();
1201 for c in key.chars() {
1202 if c.is_ascii_uppercase() {
1203 s.push('-');
1204 s.push(c.to_ascii_lowercase());
1205 } else {
1206 s.push(c);
1207 }
1208 }
1209 s
1210}
1211
1212fn camel_to_data_attr(key: &str) -> String {
1214 let mut s = String::from("data-");
1215 for c in key.chars() {
1216 if c.is_ascii_uppercase() {
1217 s.push('-');
1218 s.push(c.to_ascii_lowercase());
1219 } else {
1220 s.push(c);
1221 }
1222 }
1223 s
1224}
1225
1226pub(crate) enum DomDisp {
1228 Element(usize),
1229 Host(String),
1230}
1231
1232enum CallKind {
1233 User(FunctionData),
1234 Native(NativeFn),
1235 Resolver(Rc<RefCell<PromiseState>>, bool),
1236}
1237
1238pub fn iterable_values(v: &Value) -> Vec<Value> {
1242 match v {
1243 Value::Str(s) => s.chars().map(|c| Value::str(c.to_string())).collect(),
1244 Value::Object(o) => {
1245 let b = o.borrow();
1246 match &b.kind {
1247 ObjKind::Array(items) => items.clone(),
1248 ObjKind::SetObj(items) => items.clone(),
1249 ObjKind::MapObj(entries) => entries
1250 .iter()
1251 .map(|(k, val)| Value::Object(Obj::array(alloc::vec![k.clone(), val.clone()])))
1252 .collect(),
1253 ObjKind::Generator(_) => Vec::new(),
1256 ObjKind::Host(t) if t == "iterator" => {
1262 let items = b.props.get("_items").cloned();
1263 let pos = b
1264 .props
1265 .get("_pos")
1266 .map(|v| v.to_number() as usize)
1267 .unwrap_or(0);
1268 match items {
1269 Some(Value::Object(arr)) => match &arr.borrow().kind {
1270 ObjKind::Array(v) => v.get(pos..).map(|s| s.to_vec()).unwrap_or_default(),
1271 _ => Vec::new(),
1272 },
1273 _ => Vec::new(),
1274 }
1275 }
1276 _ => Vec::new(),
1277 }
1278 }
1279 _ => Vec::new(),
1280 }
1281}
1282
1283fn to_property_key(v: &Value) -> String {
1284 match v {
1285 Value::Number(n) => fmt_number(*n),
1286 Value::Str(s) => (**s).clone(),
1287 _ => v.to_js_string(),
1288 }
1289}
1290
1291fn shift_amount(b: &super::bigint::BigInt) -> Option<i64> {
1294 let f = b.to_f64();
1295 if !f.is_finite() {
1296 return None;
1297 }
1298 if libm::fabs(f) >= 2147483648.0 {
1300 return None;
1301 }
1302 Some(f as i64)
1303}
1304
1305fn to_i32(n: f64) -> i32 {
1306 if !n.is_finite() {
1307 return 0;
1308 }
1309 let m = libm::trunc(n);
1310 (m as i64 as u32) as i32
1311}
1312fn to_u32(n: f64) -> u32 {
1313 to_i32(n) as u32
1314}
1315
1316fn powf(base: f64, exp: f64) -> f64 {
1317 if exp == 0.0 {
1319 return 1.0;
1320 }
1321 libm::pow(base, exp)
1323}
1324
1325fn loose_eq(l: &Value, r: &Value) -> bool {
1326 match (l, r) {
1327 (Value::Null, Value::Undefined) | (Value::Undefined, Value::Null) => true,
1328 (Value::Null, Value::Null) | (Value::Undefined, Value::Undefined) => true,
1329 (Value::Number(a), Value::Number(b)) => a == b,
1330 (Value::Str(a), Value::Str(b)) => a == b,
1331 (Value::Bool(a), Value::Bool(b)) => a == b,
1332 (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
1333 (Value::Object(o), other) | (other, Value::Object(o))
1338 if !matches!(other, Value::Object(_)) =>
1339 {
1340 if matches!(o.borrow().kind, ObjKind::DateObj(_)) {
1341 l.to_number() == r.to_number()
1342 } else {
1343 loose_eq(&Value::str(Value::Object(o.clone()).to_js_string()), other)
1344 }
1345 }
1346 (Value::Number(_), Value::Str(_))
1348 | (Value::Str(_), Value::Number(_))
1349 | (Value::Bool(_), _)
1350 | (_, Value::Bool(_)) => l.to_number() == r.to_number(),
1351 _ => false,
1352 }
1353}
1354
1355fn cmp_f64(a: f64, b: f64) -> Option<core::cmp::Ordering> {
1357 if a.is_nan() || b.is_nan() {
1358 return None;
1359 }
1360 Some(if a < b {
1361 core::cmp::Ordering::Less
1362 } else if a > b {
1363 core::cmp::Ordering::Greater
1364 } else {
1365 core::cmp::Ordering::Equal
1366 })
1367}
1368
1369fn cmp(l: &Value, r: &Value, pred: fn(core::cmp::Ordering) -> bool) -> Value {
1370 if let (Value::Str(a), Value::Str(b)) = (l, r) {
1372 return Value::Bool(pred(a.as_str().cmp(b.as_str())));
1373 }
1374 let a = l.to_number();
1375 let b = r.to_number();
1376 if a.is_nan() || b.is_nan() {
1377 return Value::Bool(false);
1378 }
1379 let ord = if a < b {
1380 core::cmp::Ordering::Less
1381 } else if a > b {
1382 core::cmp::Ordering::Greater
1383 } else {
1384 core::cmp::Ordering::Equal
1385 };
1386 Value::Bool(pred(ord))
1387}