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 call_names: Vec::new(),
208 }
209 }
210
211 pub fn set_page_url(&mut self, url: &str) {
214 self.base_url = String::from(url);
215 super::builtins::update_location(&self.global, url);
216 }
217
218 pub fn define_module(&mut self, specifier: &str, source: &str) {
221 self.modules.borrow_mut().insert(
222 String::from(specifier),
223 ModuleRecord {
224 source: String::from(source),
225 exports: None,
226 },
227 );
228 }
229
230 pub fn eval(&mut self, source: &str) -> Result<Value, String> {
236 let mut parser = Parser::new(Lexer::new(source));
237 let program = parser.parse_program();
238 let total_errors = parser.error_count();
239 self.last_syntax_errors = parser.take_errors();
240 if total_errors > 0 {
241 crate::warn!(
242 "[JS] {} syntax error(s) recovered; executing best-effort",
243 total_errors
244 );
245 for e in self.last_syntax_errors.iter().take(5) {
246 crate::warn!(
247 "[JS] SyntaxError: {} (token #{})",
248 e.message,
249 e.token_index
250 );
251 }
252 }
253 let mut interp = self.new_interp();
254 let scope = self.global.clone();
255 let result = interp.exec_statements(&program.body, &scope, &Value::Undefined);
256 interp.run_event_loop();
258 super::builtins::fire_intersection_observers(&mut interp);
260 super::builtins::flush_mutation_observers(&mut interp);
262 self.out.push_str(&interp.out);
264 if interp.aborted {
265 return Err(String::from("script aborted (step/depth budget exceeded)"));
266 }
267 match result {
268 Ok(Completion::Normal(v)) | Ok(Completion::Return(v)) => Ok(v),
269 Ok(_) => Ok(Value::Undefined),
270 Err(thrown) => {
271 let err_str = thrown.to_js_string();
272 if err_str == "[object Object]" {
273 if let Value::Object(o) = &thrown {
274 let name = o.borrow().props.get("name").map(|v| v.to_js_string()).unwrap_or_else(|| alloc::string::String::from("Error"));
275 let msg = o.borrow().props.get("message").map(|v| v.to_js_string()).unwrap_or_else(|| alloc::string::String::from(""));
276 let stack = o.borrow().props.get("stack").map(|v| v.to_js_string()).unwrap_or_else(|| alloc::string::String::from(""));
277 crate::println!("[JS_ERR] {}: {}\nStack:\n{}", name, msg, stack);
278 }
279 }
280 Err(err_str)
281 }
282 }
283 }
284
285 pub fn dispatch_click(&mut self, node_idx: usize) -> bool {
288 self.dispatch_event_with(node_idx, "click", &[]).0
289 }
290
291 pub fn dispatch_event(&mut self, node_idx: usize, event_type: &str) -> bool {
293 if matches!(event_type, "input" | "change") {
301 self.dom.borrow_mut().set_attr(node_idx, "_user_interacted", "1");
302 }
303 self.dispatch_event_with(node_idx, event_type, &[]).0
304 }
305
306 pub fn dispatch_mouse(
309 &mut self,
310 node_idx: usize,
311 event_type: &str,
312 x: i32,
313 y: i32,
314 ) -> (bool, bool) {
315 self.dispatch_mouse_full(node_idx, event_type, x, y, 0, 0)
316 }
317
318 pub fn dispatch_mouse_full(
323 &mut self,
324 node_idx: usize,
325 event_type: &str,
326 x: i32,
327 y: i32,
328 button: i32,
329 wheel_delta: i32,
330 ) -> (bool, bool) {
331 let buttons = match event_type {
333 "mousedown" => match button {
334 0 => 1,
335 2 => 2,
336 1 => 4,
337 _ => 0,
338 },
339 _ => 0,
340 };
341 let detail = match event_type {
343 "click" | "mousedown" | "mouseup" => 1.0,
344 "dblclick" => 2.0,
345 _ => 0.0,
346 };
347 let (ox, oy) = (x, y);
350 let mut extra = alloc::vec![
351 (String::from("clientX"), Value::Number(x as f64)),
352 (String::from("clientY"), Value::Number(y as f64)),
353 (String::from("pageX"), Value::Number(x as f64)),
354 (String::from("pageY"), Value::Number(y as f64)),
355 (String::from("screenX"), Value::Number(x as f64)),
356 (String::from("screenY"), Value::Number(y as f64)),
357 (String::from("offsetX"), Value::Number(ox as f64)),
358 (String::from("offsetY"), Value::Number(oy as f64)),
359 (String::from("movementX"), Value::Number(0.0)),
360 (String::from("movementY"), Value::Number(0.0)),
361 (String::from("button"), Value::Number(button as f64)),
362 (String::from("buttons"), Value::Number(buttons as f64)),
363 (String::from("detail"), Value::Number(detail)),
364 ];
365 if event_type == "wheel" {
367 extra.push((String::from("deltaX"), Value::Number(0.0)));
368 extra.push((
369 String::from("deltaY"),
370 Value::Number((wheel_delta * 100) as f64),
371 ));
372 extra.push((String::from("deltaZ"), Value::Number(0.0)));
373 extra.push((String::from("deltaMode"), Value::Number(0.0))); }
375 Self::push_modifier_props(&mut extra);
376 self.dispatch_event_with(node_idx, event_type, &extra)
377 }
378
379 fn push_modifier_props(extra: &mut Vec<(String, Value)>) {
381 extra.push((
382 String::from("shiftKey"),
383 Value::Bool(crate::kernel::keyboard::shift_down()),
384 ));
385 extra.push((
386 String::from("ctrlKey"),
387 Value::Bool(crate::kernel::keyboard::ctrl_down()),
388 ));
389 extra.push((
390 String::from("altKey"),
391 Value::Bool(crate::kernel::keyboard::alt_down()),
392 ));
393 extra.push((
394 String::from("metaKey"),
395 Value::Bool(crate::kernel::keyboard::meta_down()),
396 ));
397 }
398
399 pub fn dispatch_key(&mut self, node_idx: usize, event_type: &str, key: &str) -> (bool, bool) {
403 self.dispatch_key_full(node_idx, event_type, key, false)
404 }
405
406 pub fn dispatch_key_full(
409 &mut self,
410 node_idx: usize,
411 event_type: &str,
412 key: &str,
413 repeat: bool,
414 ) -> (bool, bool) {
415 let (key_code, code, location) = Self::key_attributes(key);
416 let mut extra = alloc::vec![
417 (String::from("key"), Value::str(key)),
418 (String::from("keyCode"), Value::Number(key_code as f64)),
419 (String::from("which"), Value::Number(key_code as f64)),
420 (
421 String::from("charCode"),
422 Value::Number(if event_type == "keypress" {
423 key_code as f64
424 } else {
425 0.0
426 })
427 ),
428 (String::from("code"), Value::str(&code)),
429 (String::from("location"), Value::Number(location as f64)),
430 (String::from("repeat"), Value::Bool(repeat)),
431 ];
432 Self::push_modifier_props(&mut extra);
433 self.dispatch_event_with(node_idx, event_type, &extra)
434 }
435
436 fn key_attributes(key: &str) -> (u32, String, u32) {
439 match key {
441 "Enter" => (13, String::from("Enter"), 0),
442 "Tab" => (9, String::from("Tab"), 0),
443 "Backspace" => (8, String::from("Backspace"), 0),
444 "Escape" => (27, String::from("Escape"), 0),
445 " " => (32, String::from("Space"), 0),
446 "Delete" => (46, String::from("Delete"), 0),
447 "ArrowLeft" => (37, String::from("ArrowLeft"), 0),
448 "ArrowUp" => (38, String::from("ArrowUp"), 0),
449 "ArrowRight" => (39, String::from("ArrowRight"), 0),
450 "ArrowDown" => (40, String::from("ArrowDown"), 0),
451 "Home" => (36, String::from("Home"), 0),
452 "End" => (35, String::from("End"), 0),
453 "PageUp" => (33, String::from("PageUp"), 0),
454 "PageDown" => (34, String::from("PageDown"), 0),
455 "Shift" => (16, String::from("ShiftLeft"), 1),
456 "Control" => (17, String::from("ControlLeft"), 1),
457 "Alt" => (18, String::from("AltLeft"), 1),
458 "Meta" => (91, String::from("MetaLeft"), 1),
459 _ => {
460 let mut chars = key.chars();
462 if let (Some(c), None) = (chars.next(), chars.clone().next()) {
463 let upper = c.to_ascii_uppercase();
464 if upper.is_ascii_alphabetic() {
465 let code = alloc::format!("Key{}", upper);
466 return (upper as u32, code, 0);
467 }
468 if c.is_ascii_digit() {
469 let code = alloc::format!("Digit{}", c);
470 return (c as u32, code, 0);
471 }
472 return (c as u32, String::new(), 0);
473 }
474 (0, String::new(), 0)
475 }
476 }
477 }
478
479 pub fn dispatch_event_with(
485 &mut self,
486 node_idx: usize,
487 event_type: &str,
488 extra: &[(String, Value)],
489 ) -> (bool, bool) {
490 let path = {
492 let dom = self.dom.borrow();
493 let mut path = alloc::vec![node_idx];
494 let mut cur = node_idx;
495 let mut guard = 0;
496 while let Some(p) = dom.nodes.get(cur).and_then(|n| n.parent) {
497 path.push(p);
498 cur = p;
499 guard += 1;
500 if guard > dom.nodes.len() {
501 break;
502 }
503 }
504 path
505 };
506 let any = path
507 .iter()
508 .any(|&n| self.dom.borrow().has_listener_on(n, event_type));
509 if !any {
510 return (false, false);
511 }
512
513 let bubbles = extra
519 .iter()
520 .find(|(k, _)| k == "bubbles")
521 .map(|(_, v)| v.truthy())
522 .unwrap_or(true);
523 let composed: Vec<Value> = path.iter().map(|&n| Value::Object(Obj::dom(n))).collect();
525
526 let target = Value::Object(Obj::dom(node_idx));
528 let ev = Obj::plain();
529 {
530 let mut e = ev.borrow_mut();
531 e.props.insert(String::from("type"), Value::str(event_type));
532 e.props.insert(String::from("target"), target.clone());
533 e.props.insert(String::from("bubbles"), Value::Bool(true));
534 e.props.insert(String::from("cancelable"), Value::Bool(true));
540 e.props.insert(String::from("isTrusted"), Value::Bool(false));
546 e.props
547 .insert(String::from("eventPhase"), Value::Number(0.0));
548 e.props
549 .insert(String::from("defaultPrevented"), Value::Bool(false));
550 e.props.insert(
553 String::from("timeStamp"),
554 Value::Number(super::builtins::next_perf_timestamp()),
555 );
556 e.props.insert(String::from("returnValue"), Value::Bool(true));
559 e.props.insert(String::from("cancelBubble"), Value::Bool(false));
560 e.props.insert(String::from("srcElement"), target.clone());
561 e.props.insert(
562 String::from("_composedPath"),
563 Value::Object(Obj::array(composed)),
564 );
565 for (k, v) in extra {
566 e.props.insert(k.clone(), v.clone());
567 }
568 e.props.insert(
569 String::from("preventDefault"),
570 Value::Object(Obj::native("preventDefault", |_, this, _| {
571 if let Value::Object(o) = &this {
572 let cancelable = o
573 .borrow()
574 .props
575 .get("cancelable")
576 .map(|v| v.truthy())
577 .unwrap_or(true);
578 if cancelable {
579 let mut b = o.borrow_mut();
580 b.props
581 .insert(String::from("defaultPrevented"), Value::Bool(true));
582 b.props.insert(String::from("returnValue"), Value::Bool(false));
586 }
587 }
588 Ok(Value::Undefined)
589 })),
590 );
591 e.props.insert(
592 String::from("stopPropagation"),
593 Value::Object(Obj::native("stopPropagation", |_, this, _| {
594 if let Value::Object(o) = &this {
595 let mut b = o.borrow_mut();
596 b.props
597 .insert(String::from("_stop"), Value::Bool(true));
598 b.props.insert(String::from("cancelBubble"), Value::Bool(true));
601 }
602 Ok(Value::Undefined)
603 })),
604 );
605 e.props.insert(
606 String::from("stopImmediatePropagation"),
607 Value::Object(Obj::native("stopImmediatePropagation", |_, this, _| {
608 if let Value::Object(o) = &this {
609 let mut b = o.borrow_mut();
610 b.props.insert(String::from("_stop"), Value::Bool(true));
611 b.props
612 .insert(String::from("_stopImmediate"), Value::Bool(true));
613 b.props.insert(String::from("cancelBubble"), Value::Bool(true));
614 }
615 Ok(Value::Undefined)
616 })),
617 );
618 e.props.insert(
619 String::from("composedPath"),
620 Value::Object(Obj::native("composedPath", |_, this, _| {
621 if let Value::Object(o) = &this {
622 if let Some(p) = o.borrow().props.get("_composedPath") {
623 return Ok(p.clone());
624 }
625 }
626 Ok(Value::Object(Obj::array(Vec::new())))
627 })),
628 );
629 }
630
631 let mut steps: Vec<(usize, bool, f64)> = Vec::new();
635 for &n in path.iter().skip(1).rev() {
637 steps.push((n, true, 1.0));
638 }
639 steps.push((node_idx, true, 2.0));
641 steps.push((node_idx, false, 2.0));
642 if bubbles {
644 for &n in path.iter().skip(1) {
645 steps.push((n, false, 3.0));
646 }
647 }
648
649 let mut fired = false;
650 let mut to_remove: Vec<u64> = Vec::new();
651 'outer: for (n, want_capture, phase) in steps {
652 let listeners = self
653 .dom
654 .borrow()
655 .listeners_phase(n, event_type, want_capture);
656 if listeners.is_empty() {
657 continue;
658 }
659 {
660 let mut e = ev.borrow_mut();
661 e.props
662 .insert(String::from("currentTarget"), Value::Object(Obj::dom(n)));
663 e.props
664 .insert(String::from("eventPhase"), Value::Number(phase));
665 }
666 let node_this = Value::Object(Obj::dom(n));
667 for (func, once, id) in listeners {
668 let mut interp = self.new_interp();
669 let ctx = alloc::format!("{} listener", event_type);
670 interp.call_listener(
671 &func,
672 node_this.clone(),
673 &[Value::Object(ev.clone())],
674 &ctx,
675 );
676 interp.run_event_loop();
677 self.out.push_str(&interp.out);
678 fired = true;
679 if once {
680 to_remove.push(id);
681 }
682 if ev
683 .borrow()
684 .props
685 .get("_stopImmediate")
686 .map(|v| v.truthy())
687 .unwrap_or(false)
688 {
689 break 'outer;
690 }
691 }
692 if ev
693 .borrow()
694 .props
695 .get("_stop")
696 .map(|v| v.truthy())
697 .unwrap_or(false)
698 {
699 break 'outer;
700 }
701 }
702 if !to_remove.is_empty() {
704 let mut dom = self.dom.borrow_mut();
705 for id in to_remove {
706 dom.remove_listener_by_id(id);
707 }
708 }
709 let default_prevented = ev
710 .borrow()
711 .props
712 .get("defaultPrevented")
713 .map(|v| v.truthy())
714 .unwrap_or(false);
715 (fired, default_prevented)
716 }
717
718 pub fn take_pending_reset(&mut self) -> Option<usize> {
721 let g = self.global.clone();
722 let mut gb = g.borrow_mut();
723 let v = match gb.vars.get("__pending_reset") {
724 Some(Value::Number(n)) if *n >= 1.0 => *n as usize - 1,
725 _ => return None,
726 };
727 gb.vars.insert("__pending_reset".into(), Value::Number(0.0));
728 Some(v)
729 }
730
731 pub fn take_pending_nav(&mut self) -> i64 {
734 let history = match self.global.borrow().vars.get("history") {
735 Some(Value::Object(h)) => h.clone(),
736 _ => return 0,
737 };
738 let mut hb = history.borrow_mut();
739 let n = match hb.props.get("_pending_nav") {
740 Some(Value::Number(n)) => *n as i64,
741 _ => 0,
742 };
743 if n != 0 {
744 hb.props.insert("_pending_nav".into(), Value::Number(0.0));
745 }
746 n
747 }
748
749 pub fn take_pending_location(&mut self) -> Option<(String, String)> {
754 let location = match self.global.borrow().vars.get("location") {
755 Some(Value::Object(l)) => l.clone(),
756 _ => return None,
757 };
758 let mut lb = location.borrow_mut();
759 let url = match lb.props.get("_pending_location") {
760 Some(v) => v.to_js_string(),
761 _ => return None,
762 };
763 let mode = lb
764 .props
765 .get("_pending_location_mode")
766 .map(|v| v.to_js_string())
767 .unwrap_or_else(|| String::from("assign"));
768 lb.props.shift_remove("_pending_location");
769 lb.props.shift_remove("_pending_location_mode");
770 Some((url, mode))
771 }
772
773 fn window_listeners(&self, key: &str) -> Vec<Value> {
780 let window = match self.global.borrow().vars.get("window") {
781 Some(Value::Object(w)) => w.clone(),
782 _ => return Vec::new(),
783 };
784 let entries = match window.borrow().props.get(key) {
785 Some(Value::Object(arr)) => match &arr.borrow().kind {
786 ObjKind::Array(items) => items.clone(),
787 _ => Vec::new(),
788 },
789 _ => Vec::new(),
790 };
791 entries
792 .iter()
793 .filter_map(|entry| {
794 let Value::Object(o) = entry else {
795 return Some(entry.clone());
796 };
797 let ObjKind::Array(items) = &o.borrow().kind else {
798 return Some(entry.clone());
799 };
800 if items.get(1).is_some_and(super::dom_bridge::is_signal_aborted) {
801 return None;
802 }
803 items.first().cloned()
804 })
805 .collect()
806 }
807
808 fn fire_window_listeners(
810 &mut self,
811 listeners: &[Value],
812 event_type: &str,
813 extra: &[(String, Value)],
814 ) {
815 if listeners.is_empty() {
816 return;
817 }
818 let ev = Obj::plain();
819 {
820 let mut e = ev.borrow_mut();
821 e.props.insert(String::from("type"), Value::str(event_type));
822 for (k, v) in extra {
823 e.props.insert(k.clone(), v.clone());
824 }
825 }
826 for cb in listeners {
827 let mut interp = self.new_interp();
828 let ctx = alloc::format!("{} listener (window)", event_type);
829 interp.call_listener(cb, Value::Undefined, &[Value::Object(ev.clone())], &ctx);
830 interp.run_event_loop();
831 self.out.push_str(&interp.out);
832 }
833 }
834
835 pub fn fire_popstate(&mut self) {
838 let listeners = self.window_listeners("_popstate_listeners");
839 let state = match self.global.borrow().vars.get("history") {
841 Some(Value::Object(h)) => h
842 .borrow()
843 .props
844 .get("state")
845 .cloned()
846 .unwrap_or(Value::Null),
847 _ => Value::Null,
848 };
849 if !listeners.is_empty() {
850 self.fire_window_listeners(&listeners, "popstate", &[(String::from("state"), state)]);
851 }
852 }
853
854 pub fn has_spa_back(&self) -> bool {
856 let history = match self.global.borrow().vars.get("history") {
857 Some(Value::Object(h)) => h.clone(),
858 _ => return false,
859 };
860 let history_borrow = history.borrow();
861 match history_borrow.props.get("_spa_back_stack") {
862 Some(Value::Object(arr)) => {
863 let arr_borrow = arr.borrow();
864 match &arr_borrow.kind {
865 ObjKind::Array(items) => !items.is_empty(),
866 _ => false,
867 }
868 }
869 _ => false,
870 }
871 }
872
873 pub fn spa_go_back(&mut self) -> Option<String> {
876 let history = match self.global.borrow().vars.get("history") {
877 Some(Value::Object(h)) => h.clone(),
878 _ => return None,
879 };
880 {
882 let cur_url = super::builtins::location_href(&self.global).unwrap_or_default();
883 let cur_state = history.borrow().props.get("state").cloned().unwrap_or(Value::Null);
884 let fwd_stack_val = history.borrow().props.get("_spa_fwd_stack").cloned();
885 let fwd_stack = match fwd_stack_val {
886 Some(Value::Object(arr)) => arr,
887 _ => Obj::array(alloc::vec![]),
888 };
889 let entry = Obj::plain();
890 {
891 let mut eb = entry.borrow_mut();
892 eb.props.insert("url".into(), Value::str(cur_url));
893 eb.props.insert("state".into(), cur_state);
894 }
895 if let ObjKind::Array(items) = &mut fwd_stack.borrow_mut().kind {
896 items.push(Value::Object(entry));
897 }
898 history.borrow_mut().props.insert("_spa_fwd_stack".into(), Value::Object(fwd_stack));
899 }
900 let (prev_url, prev_state) = {
902 let back_stack_val = history.borrow().props.get("_spa_back_stack").cloned();
903 let back_stack = match back_stack_val {
904 Some(Value::Object(arr)) => arr,
905 _ => return None,
906 };
907 let entry = match &mut back_stack.borrow_mut().kind {
908 ObjKind::Array(items) => items.pop(),
909 _ => None,
910 }?;
911 let eb = match entry { Value::Object(o) => o, _ => return None };
912 let url = eb.borrow().props.get("url").cloned().unwrap_or(Value::Undefined).to_js_string();
913 let state = eb.borrow().props.get("state").cloned().unwrap_or(Value::Null);
914 (url, state)
915 };
916 history.borrow_mut().props.insert("state".into(), prev_state);
918 super::builtins::update_location(&self.global, &prev_url);
919 self.base_url = prev_url.to_string();
920 self.fire_popstate();
921 Some(prev_url.to_string())
922 }
923
924 pub fn spa_go_forward(&mut self) -> Option<String> {
926 let history = match self.global.borrow().vars.get("history") {
927 Some(Value::Object(h)) => h.clone(),
928 _ => return None,
929 };
930 {
932 let cur_url = super::builtins::location_href(&self.global).unwrap_or_default();
933 let cur_state = history.borrow().props.get("state").cloned().unwrap_or(Value::Null);
934 let back_stack_val = history.borrow().props.get("_spa_back_stack").cloned();
935 let back_stack = match back_stack_val {
936 Some(Value::Object(arr)) => arr,
937 _ => Obj::array(alloc::vec![]),
938 };
939 let entry = Obj::plain();
940 {
941 let mut eb = entry.borrow_mut();
942 eb.props.insert("url".into(), Value::str(cur_url));
943 eb.props.insert("state".into(), cur_state);
944 }
945 if let ObjKind::Array(items) = &mut back_stack.borrow_mut().kind {
946 items.push(Value::Object(entry));
947 }
948 history.borrow_mut().props.insert("_spa_back_stack".into(), Value::Object(back_stack));
949 }
950 let (next_url, next_state) = {
952 let fwd_stack_val = history.borrow().props.get("_spa_fwd_stack").cloned();
953 let fwd_stack = match fwd_stack_val {
954 Some(Value::Object(arr)) => arr,
955 _ => return None,
956 };
957 let entry = match &mut fwd_stack.borrow_mut().kind {
958 ObjKind::Array(items) => items.pop(),
959 _ => None,
960 }?;
961 let eb = match entry { Value::Object(o) => o, _ => return None };
962 let url = eb.borrow().props.get("url").cloned().unwrap_or(Value::Undefined).to_js_string();
963 let state = eb.borrow().props.get("state").cloned().unwrap_or(Value::Null);
964 (url, state)
965 };
966 history.borrow_mut().props.insert("state".into(), next_state);
967 super::builtins::update_location(&self.global, &next_url);
968 self.base_url = next_url.to_string();
969 self.fire_popstate();
970 Some(next_url.to_string())
971 }
972
973 pub fn has_spa_forward(&self) -> bool {
975 let history = match self.global.borrow().vars.get("history") {
976 Some(Value::Object(h)) => h.clone(),
977 _ => return false,
978 };
979 let history_borrow = history.borrow();
980 match history_borrow.props.get("_spa_fwd_stack") {
981 Some(Value::Object(arr)) => {
982 let arr_borrow = arr.borrow();
983 match &arr_borrow.kind {
984 ObjKind::Array(items) => !items.is_empty(),
985 _ => false,
986 }
987 }
988 _ => false,
989 }
990 }
991
992 pub fn fire_scroll(&mut self, scroll_y: i32) {
995 {
998 let g = self.global.borrow();
999 if let Some(Value::Object(w)) = g.vars.get("window") {
1000 let mut wb = w.borrow_mut();
1001 wb.props
1002 .insert("scrollY".into(), Value::Number(scroll_y as f64));
1003 wb.props
1004 .insert("pageYOffset".into(), Value::Number(scroll_y as f64));
1005 }
1006 }
1007 {
1008 let mut g = self.global.borrow_mut();
1009 g.vars
1010 .insert("scrollY".into(), Value::Number(scroll_y as f64));
1011 g.vars
1012 .insert("pageYOffset".into(), Value::Number(scroll_y as f64));
1013 }
1014 let listeners = self.window_listeners("_scroll_listeners");
1015 self.fire_window_listeners(
1016 &listeners,
1017 "scroll",
1018 &[(String::from("scrollY"), Value::Number(scroll_y as f64))],
1019 );
1020 }
1021
1022 pub fn fire_hashchange(&mut self, old_url: &str, new_url: &str) {
1025 let new_hash = match new_url.split_once('#') {
1027 Some((_, frag)) => alloc::format!("#{}", frag),
1028 None => String::new(),
1029 };
1030 {
1031 let g = self.global.borrow();
1032 if let Some(Value::Object(loc)) = g.vars.get("location") {
1033 loc.borrow_mut()
1034 .props
1035 .insert("hash".into(), Value::str(&new_hash));
1036 }
1037 }
1038 let listeners = self.window_listeners("_hashchange_listeners");
1039 self.fire_window_listeners(
1040 &listeners,
1041 "hashchange",
1042 &[
1043 (String::from("oldURL"), Value::str(old_url)),
1044 (String::from("newURL"), Value::str(new_url)),
1045 ],
1046 );
1047 }
1048
1049 pub fn fire_resize(&mut self, width: i32, height: i32) {
1052 {
1053 let g = self.global.borrow();
1054 if let Some(Value::Object(w)) = g.vars.get("window") {
1055 let mut wb = w.borrow_mut();
1056 wb.props
1057 .insert("innerWidth".into(), Value::Number(width as f64));
1058 wb.props
1059 .insert("innerHeight".into(), Value::Number(height as f64));
1060 }
1061 }
1062 {
1063 let mut g = self.global.borrow_mut();
1065 g.vars
1066 .insert("innerWidth".into(), Value::Number(width as f64));
1067 g.vars
1068 .insert("innerHeight".into(), Value::Number(height as f64));
1069 }
1070 let listeners = self.window_listeners("_resize_listeners");
1071 self.fire_window_listeners(
1072 &listeners,
1073 "resize",
1074 &[
1075 (String::from("innerWidth"), Value::Number(width as f64)),
1076 (String::from("innerHeight"), Value::Number(height as f64)),
1077 ],
1078 );
1079 }
1080}
1081
1082pub struct Interp {
1084 pub steps: u64,
1085 pub max_steps: u64,
1086 pub depth: u32,
1087 pub max_depth: u32,
1088 pub aborted: bool,
1089 pub out: String,
1090 pub dom: Rc<RefCell<DomBridge>>,
1092 pub microtasks: Rc<RefCell<VecDeque<Job>>>,
1094 gen_replay: Option<GenReplay>,
1096 pub macrotasks: Rc<RefCell<VecDeque<(Value, Vec<Value>, u64)>>>,
1098 pub base_url: String,
1100 pub global: Rc<RefCell<Scope>>,
1102 pub io_callbacks: Vec<Value>,
1104 pub intervals: Vec<(u64, Value, Vec<Value>, u32)>,
1106 pub modules: ModuleRegistry,
1108 pending_label: Option<String>,
1113 pending_new_target: Option<Value>,
1117 new_target_stack: Vec<Value>,
1120 pub call_names: Vec<String>,
1123}
1124
1125type EvalResult = Result<Value, Value>;
1126
1127
1128mod exec;
1133mod iter_gen;
1134mod eval_expr;
1135mod promise_loop;
1136mod operators;
1137mod calls;
1138mod properties;
1139mod dom_props;
1140
1141
1142fn aria_attr_name(prop: &str) -> Option<&'static str> {
1148 Some(match prop {
1149 "role" => "role",
1150 "ariaLabel" => "aria-label",
1151 "ariaLabelledBy" => "aria-labelledby",
1152 "ariaDescribedBy" => "aria-describedby",
1153 "ariaHidden" => "aria-hidden",
1154 "ariaExpanded" => "aria-expanded",
1155 "ariaChecked" => "aria-checked",
1156 "ariaSelected" => "aria-selected",
1157 "ariaDisabled" => "aria-disabled",
1158 "ariaPressed" => "aria-pressed",
1159 "ariaCurrent" => "aria-current",
1160 "ariaLive" => "aria-live",
1161 "ariaBusy" => "aria-busy",
1162 "ariaRequired" => "aria-required",
1163 "ariaInvalid" => "aria-invalid",
1164 "ariaValueNow" => "aria-valuenow",
1165 "ariaValueMin" => "aria-valuemin",
1166 "ariaValueMax" => "aria-valuemax",
1167 "ariaValueText" => "aria-valuetext",
1168 "ariaControls" => "aria-controls",
1169 "ariaOwns" => "aria-owns",
1170 "ariaModal" => "aria-modal",
1171 "ariaMultiline" => "aria-multiline",
1172 "ariaMultiSelectable" => "aria-multiselectable",
1173 "ariaOrientation" => "aria-orientation",
1174 "ariaPlaceholder" => "aria-placeholder",
1175 "ariaReadOnly" => "aria-readonly",
1176 "ariaRoleDescription" => "aria-roledescription",
1177 "ariaSort" => "aria-sort",
1178 "ariaAtomic" => "aria-atomic",
1179 "ariaHasPopup" => "aria-haspopup",
1180 "ariaColCount" => "aria-colcount",
1183 "ariaColIndex" => "aria-colindex",
1184 "ariaColSpan" => "aria-colspan",
1185 "ariaRowCount" => "aria-rowcount",
1186 "ariaRowIndex" => "aria-rowindex",
1187 "ariaRowSpan" => "aria-rowspan",
1188 "ariaSetSize" => "aria-setsize",
1189 "ariaPosInSet" => "aria-posinset",
1190 "ariaLevel" => "aria-level",
1191 "ariaKeyShortcuts" => "aria-keyshortcuts",
1192 "ariaAutoComplete" => "aria-autocomplete",
1193 "ariaDetails" => "aria-details",
1194 "ariaErrorMessage" => "aria-errormessage",
1195 "ariaFlowTo" => "aria-flowto",
1196 "ariaRelevant" => "aria-relevant",
1197 _ => return None,
1198 })
1199}
1200
1201fn camel_to_kebab(key: &str) -> String {
1204 let mut s = String::new();
1205 for c in key.chars() {
1206 if c.is_ascii_uppercase() {
1207 s.push('-');
1208 s.push(c.to_ascii_lowercase());
1209 } else {
1210 s.push(c);
1211 }
1212 }
1213 s
1214}
1215
1216fn camel_to_data_attr(key: &str) -> String {
1218 let mut s = String::from("data-");
1219 for c in key.chars() {
1220 if c.is_ascii_uppercase() {
1221 s.push('-');
1222 s.push(c.to_ascii_lowercase());
1223 } else {
1224 s.push(c);
1225 }
1226 }
1227 s
1228}
1229
1230pub(crate) enum DomDisp {
1232 Element(usize),
1233 Host(String),
1234}
1235
1236enum CallKind {
1237 User(FunctionData),
1238 Native(NativeFn),
1239 Resolver(Rc<RefCell<PromiseState>>, bool),
1240}
1241
1242pub fn iterable_values(v: &Value) -> Vec<Value> {
1246 match v {
1247 Value::Str(s) => s.chars().map(|c| Value::str(c.to_string())).collect(),
1248 Value::Object(o) => {
1249 let b = o.borrow();
1250 match &b.kind {
1251 ObjKind::Array(items) => items.clone(),
1252 ObjKind::SetObj(items) => items.clone(),
1253 ObjKind::MapObj(entries) => entries
1254 .iter()
1255 .map(|(k, val)| Value::Object(Obj::array(alloc::vec![k.clone(), val.clone()])))
1256 .collect(),
1257 ObjKind::Generator(_) => Vec::new(),
1260 ObjKind::Host(t) if t == "iterator" => {
1266 let items = b.props.get("_items").cloned();
1267 let pos = b
1268 .props
1269 .get("_pos")
1270 .map(|v| v.to_number() as usize)
1271 .unwrap_or(0);
1272 match items {
1273 Some(Value::Object(arr)) => match &arr.borrow().kind {
1274 ObjKind::Array(v) => v.get(pos..).map(|s| s.to_vec()).unwrap_or_default(),
1275 _ => Vec::new(),
1276 },
1277 _ => Vec::new(),
1278 }
1279 }
1280 _ => Vec::new(),
1281 }
1282 }
1283 _ => Vec::new(),
1284 }
1285}
1286
1287fn to_property_key(v: &Value) -> String {
1288 match v {
1289 Value::Number(n) => fmt_number(*n),
1290 Value::Str(s) => (**s).clone(),
1291 _ => v.to_js_string(),
1292 }
1293}
1294
1295fn shift_amount(b: &super::bigint::BigInt) -> Option<i64> {
1298 let f = b.to_f64();
1299 if !f.is_finite() {
1300 return None;
1301 }
1302 if libm::fabs(f) >= 2147483648.0 {
1304 return None;
1305 }
1306 Some(f as i64)
1307}
1308
1309fn to_i32(n: f64) -> i32 {
1310 if !n.is_finite() {
1311 return 0;
1312 }
1313 let m = libm::trunc(n);
1314 (m as i64 as u32) as i32
1315}
1316fn to_u32(n: f64) -> u32 {
1317 to_i32(n) as u32
1318}
1319
1320fn powf(base: f64, exp: f64) -> f64 {
1321 if exp == 0.0 {
1323 return 1.0;
1324 }
1325 libm::pow(base, exp)
1327}
1328
1329fn loose_eq(l: &Value, r: &Value) -> bool {
1330 match (l, r) {
1331 (Value::Null, Value::Undefined) | (Value::Undefined, Value::Null) => true,
1332 (Value::Null, Value::Null) | (Value::Undefined, Value::Undefined) => true,
1333 (Value::Number(a), Value::Number(b)) => a == b,
1334 (Value::Str(a), Value::Str(b)) => a == b,
1335 (Value::Bool(a), Value::Bool(b)) => a == b,
1336 (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
1337 (Value::Object(o), other) | (other, Value::Object(o))
1342 if !matches!(other, Value::Object(_)) =>
1343 {
1344 if matches!(o.borrow().kind, ObjKind::DateObj(_)) {
1345 l.to_number() == r.to_number()
1346 } else {
1347 loose_eq(&Value::str(Value::Object(o.clone()).to_js_string()), other)
1348 }
1349 }
1350 (Value::Number(_), Value::Str(_))
1352 | (Value::Str(_), Value::Number(_))
1353 | (Value::Bool(_), _)
1354 | (_, Value::Bool(_)) => l.to_number() == r.to_number(),
1355 _ => false,
1356 }
1357}
1358
1359fn cmp_f64(a: f64, b: f64) -> Option<core::cmp::Ordering> {
1361 if a.is_nan() || b.is_nan() {
1362 return None;
1363 }
1364 Some(if a < b {
1365 core::cmp::Ordering::Less
1366 } else if a > b {
1367 core::cmp::Ordering::Greater
1368 } else {
1369 core::cmp::Ordering::Equal
1370 })
1371}
1372
1373fn cmp(l: &Value, r: &Value, pred: fn(core::cmp::Ordering) -> bool) -> Value {
1374 if let (Value::Str(a), Value::Str(b)) = (l, r) {
1376 return Value::Bool(pred(a.as_str().cmp(b.as_str())));
1377 }
1378 let a = l.to_number();
1379 let b = r.to_number();
1380 if a.is_nan() || b.is_nan() {
1381 return Value::Bool(false);
1382 }
1383 let ord = if a < b {
1384 core::cmp::Ordering::Less
1385 } else if a > b {
1386 core::cmp::Ordering::Greater
1387 } else {
1388 core::cmp::Ordering::Equal
1389 };
1390 Value::Bool(pred(ord))
1391}