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 = 30_000_000;
26pub(crate) const DOCUMENT_REFERRER_KEY: &str = "\u{0}document.referrer";
28const DEFAULT_MAX_DEPTH: u32 = 2000;
29pub(crate) const JS_STACK_BUDGET: usize = 640 * 1024;
31pub(crate) const JS_STACK_MARGIN: usize = 128 * 1024;
33
34pub(crate) fn js_stack_window() -> (usize, usize) {
36 let sp = current_sp();
37 match crate::kernel::scheduler::current_stack_bounds() {
38 Some((lo, hi)) => (lo + JS_STACK_MARGIN, hi),
39 None => (sp.saturating_sub(JS_STACK_BUDGET), sp + 64 * 1024),
40 }
41}
42
43#[inline(always)]
45pub(crate) fn current_sp() -> usize {
46 let sp: usize;
47 unsafe { core::arch::asm!("mov {}, sp", out(reg) sp, options(nomem, nostack, preserves_flags)) };
49 sp
50}
51
52const MODULE_EXPORTS_KEY: &str = "\u{0}module.exports";
55
56pub struct ModuleRecord {
60 pub source: String,
61 pub exports: Option<Rc<RefCell<BTreeMap<String, Value>>>>,
62}
63
64pub type ModuleRegistry = Rc<RefCell<BTreeMap<String, ModuleRecord>>>;
66
67pub struct Scope {
69 pub vars: BTreeMap<String, Value>,
70 pub parent: Option<Rc<RefCell<Scope>>>,
71 pub is_block: bool,
74 pub gc_slot: u32,
76}
77
78impl Drop for Scope {
79 fn drop(&mut self) {
80 super::cycle_gc::untrack_scope(self.gc_slot);
81 }
82}
83
84impl Scope {
85 pub fn new_root() -> Rc<RefCell<Scope>> {
86 super::cycle_gc::track_scope(Rc::new(RefCell::new(Scope {
87 vars: BTreeMap::new(),
88 parent: None,
89 is_block: false,
90 gc_slot: super::cycle_gc::NO_SLOT,
91 })))
92 }
93 pub fn child(parent: Rc<RefCell<Scope>>) -> Rc<RefCell<Scope>> {
94 super::cycle_gc::track_scope(Rc::new(RefCell::new(Scope {
95 vars: BTreeMap::new(),
96 parent: Some(parent),
97 is_block: false,
98 gc_slot: super::cycle_gc::NO_SLOT,
99 })))
100 }
101 pub fn block(parent: Rc<RefCell<Scope>>) -> Rc<RefCell<Scope>> {
103 super::cycle_gc::track_scope(Rc::new(RefCell::new(Scope {
104 vars: BTreeMap::new(),
105 parent: Some(parent),
106 is_block: true,
107 gc_slot: super::cycle_gc::NO_SLOT,
108 })))
109 }
110}
111
112pub(crate) fn var_scope(scope: &Rc<RefCell<Scope>>) -> Rc<RefCell<Scope>> {
114 let mut cur = scope.clone();
115 loop {
116 let next = {
117 let b = cur.borrow();
118 if !b.is_block {
119 None
120 } else {
121 b.parent.clone()
122 }
123 };
124 match next {
125 Some(p) => cur = p,
126 None => return cur,
127 }
128 }
129}
130
131pub(crate) fn block_has_lexical(stmts: &[Statement]) -> bool {
133 stmts.iter().any(|s| {
134 matches!(
135 s,
136 Statement::VarDeclaration { kind, .. } if !matches!(kind, VarKind::Var)
137 )
138 })
139}
140
141pub(crate) fn scope_get(scope: &Rc<RefCell<Scope>>, name: &str) -> Option<Value> {
142 let b = scope.borrow();
143 if let Some(v) = b.vars.get(name) {
144 return Some(v.clone());
145 }
146 match &b.parent {
147 Some(p) => scope_get(p, name),
148 None => None,
149 }
150}
151
152fn scope_assign(scope: &Rc<RefCell<Scope>>, name: &str, val: Value) -> bool {
154 {
155 let mut b = scope.borrow_mut();
156 if b.vars.contains_key(name) {
157 b.vars.insert(name.to_string(), val);
158 return true;
159 }
160 }
161 let parent = scope.borrow().parent.clone();
162 match parent {
163 Some(p) => scope_assign(&p, name, val),
164 None => false,
165 }
166}
167
168fn scope_declare(scope: &Rc<RefCell<Scope>>, name: &str, val: Value) {
169 scope.borrow_mut().vars.insert(name.to_string(), val);
170}
171
172pub(crate) fn infer_function_name(v: &Value, name: &str) {
177 if let Value::Object(o) = v {
178 if let ObjKind::Function(fd) = &mut o.borrow_mut().kind {
179 if fd.name.is_empty() {
180 fd.name = String::from(name);
181 }
182 }
183 }
184}
185
186#[derive(Clone)]
192pub struct TimerTask {
193 pub cb: Value,
194 pub args: Vec<Value>,
195 pub id: u64,
196 pub due_ms: u64,
197 pub period_ms: Option<u64>,
198}
199
200pub(crate) enum Completion {
202 Normal(Value),
203 Return(Value),
204 Break(Option<String>),
205 Continue(Option<String>),
206}
207
208struct GenReplay {
213 sent: Vec<super::value::GenCompletion>,
215 counter: usize,
217 target: usize,
219 suspending: bool,
221 yielded: Value,
223 returning: Option<Value>,
225}
226
227pub struct JsRuntime {
229 pub global: Rc<RefCell<Scope>>,
230 pub out: String,
232 pub dom: Rc<RefCell<DomBridge>>,
234 pub microtasks: Rc<RefCell<VecDeque<Job>>>,
236 pub macrotasks: Rc<RefCell<VecDeque<TimerTask>>>,
238 pub clock_skew: Rc<core::cell::Cell<u64>>,
240 pub base_url: String,
242 pub modules: ModuleRegistry,
244 pub last_syntax_errors: Vec<super::parser::ParseError>,
251}
252
253impl Default for JsRuntime {
254 fn default() -> Self {
255 Self::new()
256 }
257}
258
259impl JsRuntime {
260 pub fn set_document_ready_state(&mut self, state: &str) {
267 let doc = scope_get(&self.global, "document");
268 if let Some(Value::Object(d)) = &doc {
269 d.borrow_mut().props.insert("readyState".into(), Value::str(state));
270 }
271 if state == "loading" {
272 return;
273 }
274 let rsc = self.window_listeners("_evt_readystatechange_listeners");
275 self.fire_window_listeners(&rsc, "readystatechange", &[]);
276 if let Some(d) = &doc {
277 self.call_on_handler(d, "onreadystatechange", "readystatechange");
278 }
279 let (key, ty) = if state == "interactive" {
280 ("_evt_DOMContentLoaded_listeners", "DOMContentLoaded")
281 } else {
282 ("_evt_load_listeners", "load")
283 };
284 let l = self.window_listeners(key);
285 self.fire_window_listeners(&l, ty, &[]);
286 if ty == "load" {
287 if let Some(w) = scope_get(&self.global, "window") {
288 self.call_on_handler(&w, "onload", "load");
289 }
290 }
291 if let Some(Value::Object(w)) = scope_get(&self.global, "window") {
293 let mut wb = w.borrow_mut();
294 wb.props.shift_remove(key);
295 if state == "complete" {
296 wb.props.shift_remove("_evt_readystatechange_listeners");
297 }
298 }
299 }
300
301 fn call_on_handler(&mut self, obj: &Value, prop: &str, ty: &str) {
303 let f = match obj {
304 Value::Object(o) => o.borrow().props.get(prop).cloned(),
305 _ => None,
306 };
307 if let Some(f @ Value::Object(_)) = f {
308 let ev = Obj::plain();
309 ev.borrow_mut().props.insert("type".into(), Value::str(ty));
310 let mut it = self.new_interp();
311 it.call_listener(&f, obj.clone(), &[Value::Object(ev)], ty);
312 it.run_event_loop();
313 self.out.push_str(&it.out);
314 }
315 }
316
317 pub fn new() -> Self {
318 let global = Scope::new_root();
319 super::builtins::install(&global);
320 JsRuntime {
321 global,
322 out: String::new(),
323 dom: Rc::new(RefCell::new(DomBridge::new())),
324 microtasks: Rc::new(RefCell::new(VecDeque::new())),
325 macrotasks: Rc::new(RefCell::new(VecDeque::new())),
326 clock_skew: Rc::new(core::cell::Cell::new(0)),
327 base_url: String::new(),
328 modules: Rc::new(RefCell::new(BTreeMap::new())),
329 last_syntax_errors: Vec::new(),
330 }
331 }
332
333 fn new_interp(&self) -> Interp {
334 let base_url = super::builtins::location_href(&self.global)
337 .filter(|s| !s.is_empty())
338 .unwrap_or_else(|| self.base_url.clone());
339 Interp {
340 steps: 0,
341 max_steps: DEFAULT_MAX_STEPS,
342 depth: 0,
343 max_depth: DEFAULT_MAX_DEPTH,
344 stack_limit: js_stack_window(),
345 aborted: false,
346 out: String::new(),
347 dom: self.dom.clone(),
348 microtasks: self.microtasks.clone(),
349 gen_replay: None,
350 macrotasks: self.macrotasks.clone(),
351 clock_skew: self.clock_skew.clone(),
352 base_url,
353 global: self.global.clone(),
354 io_callbacks: Vec::new(),
355 modules: self.modules.clone(),
356 pending_label: None,
357 pending_new_target: None,
358 ce_upgrade_target: None,
359 ce_processing: false,
360 ce_stack: Vec::new(),
361 dom_builtin_lookup: false,
362 new_target_stack: Vec::new(),
363 call_names: Vec::new(),
364 tmp_bodies: Vec::new(), }
366 }
367
368 pub fn set_page_url(&mut self, url: &str) {
371 self.base_url = String::from(url);
372 super::builtins::update_location(&self.global, url);
373 }
374
375 pub fn set_referrer(&mut self, referrer: &str) {
377 self.global
378 .borrow_mut()
379 .vars
380 .insert(String::from(DOCUMENT_REFERRER_KEY), Value::str(referrer));
381 }
382
383 pub fn set_frame_depth(&mut self, depth: u32) {
386 super::builtins::configure_frame_depth(&self.global, depth);
387 }
388
389 pub fn deliver_message(&mut self, data: Value, origin: &str) {
392 let mut interp = self.new_interp();
393 super::builtins::deliver_window_message(&mut interp, data, origin);
394 interp.run_event_loop();
395 self.out.push_str(&interp.out);
396 }
397
398 pub fn define_module(&mut self, specifier: &str, source: &str) {
401 self.modules.borrow_mut().insert(
402 String::from(specifier),
403 ModuleRecord {
404 source: String::from(source),
405 exports: None,
406 },
407 );
408 }
409
410 pub fn pump_timers(&mut self, budget_ms: u64) -> bool {
413 let interp = self.new_interp();
414 if !interp.has_due_timer() && self.microtasks.borrow().is_empty() {
415 return false;
416 }
417 let mut interp = interp;
418 interp.run_event_loop_for(budget_ms);
419 super::builtins::flush_mutation_observers(&mut interp);
420 self.out.push_str(&interp.out);
421 true
422 }
423
424 pub fn has_due_timers(&self) -> bool {
426 let now = crate::kernel::timer::get_system_time_ms().wrapping_add(self.clock_skew.get());
427 self.macrotasks.try_borrow().is_ok_and(|q| q.iter().any(|t| t.due_ms <= now))
428 }
429
430 pub fn fast_forward_timers(&mut self, max_ms: u64) {
432 let mut interp = self.new_interp();
433 let start = interp.timer_now();
434 loop {
435 interp.run_microtasks();
436 if interp.aborted {
437 break;
438 }
439 let next = interp.macrotasks.borrow().iter().map(|t| t.due_ms).min();
440 let Some(due) = next else { break };
441 if due.saturating_sub(start) > max_ms {
442 break;
443 }
444 interp.run_timer(true);
445 }
446 self.out.push_str(&interp.out);
447 }
448
449 pub fn eval(&mut self, source: &str) -> Result<Value, String> {
455 let is_large = source.len() >= 65_536;
456 let mut parser = Parser::new_lazy(Lexer::new(source));
457 let program = parser.parse_program();
458 let total_errors = parser.error_count();
459 self.last_syntax_errors = parser.take_errors();
460 drop(parser);
463 if total_errors > 0 {
464 crate::warn!(
465 "[JS] {} syntax error(s) recovered; executing best-effort",
466 total_errors
467 );
468 for e in self.last_syntax_errors.iter().take(5) {
469 crate::warn!(
470 "[JS] SyntaxError: {} (token #{})",
471 e.message,
472 e.token_index
473 );
474 }
475 }
476 let mut interp = self.new_interp();
477 let scope = self.global.clone();
478 let global_this = self.global.borrow().vars.get("window").cloned().unwrap_or(Value::Undefined);
482 let result = interp.exec_statements(&program.body, &scope, &global_this);
483 drop(program);
484 interp.run_event_loop();
486 super::builtins::fire_intersection_observers(&mut interp);
488 super::builtins::fire_resize_observers(&mut interp);
490 super::builtins::flush_mutation_observers(&mut interp);
492 #[cfg(target_os = "none")]
493 if is_large {
494 let _ = super::cycle_gc::collect(true);
495 }
496 let _ = is_large;
497 self.out.push_str(&interp.out);
499 if interp.aborted {
500 return Err(String::from("script aborted (step/depth budget exceeded)"));
501 }
502 match result {
503 Ok(Completion::Normal(v)) | Ok(Completion::Return(v)) => Ok(v),
504 Ok(_) => Ok(Value::Undefined),
505 Err(thrown) => {
506 let err_str = thrown.to_js_string();
507 if err_str == "[object Object]" {
508 if let Value::Object(o) = &thrown {
509 let name = o.borrow().props.get("name").map(|v| v.to_js_string()).unwrap_or_else(|| alloc::string::String::from("Error"));
510 let msg = o.borrow().props.get("message").map(|v| v.to_js_string()).unwrap_or_else(|| alloc::string::String::from(""));
511 let stack = o.borrow().props.get("stack").map(|v| v.to_js_string()).unwrap_or_else(|| alloc::string::String::from(""));
512 crate::println!("[JS_ERR] {}: {}\nStack:\n{}", name, msg, stack);
513 }
514 }
515 Err(err_str)
516 }
517 }
518 }
519
520 pub fn dispatch_click(&mut self, node_idx: usize) -> bool {
523 self.dispatch_event_with(node_idx, "click", &[]).0
524 }
525
526 pub fn dispatch_event(&mut self, node_idx: usize, event_type: &str) -> bool {
528 if matches!(event_type, "input" | "change") {
536 self.dom.borrow_mut().set_attr(node_idx, "_user_interacted", "1");
537 }
538 self.dispatch_event_with(node_idx, event_type, &[]).0
539 }
540
541 pub fn dispatch_mouse(
544 &mut self,
545 node_idx: usize,
546 event_type: &str,
547 x: i32,
548 y: i32,
549 ) -> (bool, bool) {
550 self.dispatch_mouse_full(node_idx, event_type, x, y, 0, 0)
551 }
552
553 pub fn dispatch_mouse_full(
558 &mut self,
559 node_idx: usize,
560 event_type: &str,
561 x: i32,
562 y: i32,
563 button: i32,
564 wheel_delta: i32,
565 ) -> (bool, bool) {
566 let buttons = match event_type {
568 "mousedown" => match button {
569 0 => 1,
570 2 => 2,
571 1 => 4,
572 _ => 0,
573 },
574 _ => 0,
575 };
576 let detail = match event_type {
578 "click" | "mousedown" | "mouseup" => 1.0,
579 "dblclick" => 2.0,
580 _ => 0.0,
581 };
582 let (ox, oy) = (x, y);
585 let mut extra = alloc::vec![
586 (String::from("clientX"), Value::Number(x as f64)),
587 (String::from("clientY"), Value::Number(y as f64)),
588 (String::from("pageX"), Value::Number(x as f64)),
589 (String::from("pageY"), Value::Number(y as f64)),
590 (String::from("screenX"), Value::Number(x as f64)),
591 (String::from("screenY"), Value::Number(y as f64)),
592 (String::from("offsetX"), Value::Number(ox as f64)),
593 (String::from("offsetY"), Value::Number(oy as f64)),
594 (String::from("movementX"), Value::Number(0.0)),
595 (String::from("movementY"), Value::Number(0.0)),
596 (String::from("button"), Value::Number(button as f64)),
597 (String::from("buttons"), Value::Number(buttons as f64)),
598 (String::from("detail"), Value::Number(detail)),
599 ];
600 if event_type == "wheel" {
602 extra.push((String::from("deltaX"), Value::Number(0.0)));
603 extra.push((
604 String::from("deltaY"),
605 Value::Number((wheel_delta * 100) as f64),
606 ));
607 extra.push((String::from("deltaZ"), Value::Number(0.0)));
608 extra.push((String::from("deltaMode"), Value::Number(0.0))); }
610 Self::push_modifier_props(&mut extra);
611 self.dispatch_event_with(node_idx, event_type, &extra)
612 }
613
614 fn push_modifier_props(extra: &mut Vec<(String, Value)>) {
616 extra.push((
617 String::from("shiftKey"),
618 Value::Bool(crate::kernel::keyboard::shift_down()),
619 ));
620 extra.push((
621 String::from("ctrlKey"),
622 Value::Bool(crate::kernel::keyboard::ctrl_down()),
623 ));
624 extra.push((
625 String::from("altKey"),
626 Value::Bool(crate::kernel::keyboard::alt_down()),
627 ));
628 extra.push((
629 String::from("metaKey"),
630 Value::Bool(crate::kernel::keyboard::meta_down()),
631 ));
632 }
633
634 pub fn dispatch_key(&mut self, node_idx: usize, event_type: &str, key: &str) -> (bool, bool) {
638 self.dispatch_key_full(node_idx, event_type, key, false)
639 }
640
641 pub fn dispatch_key_full(
644 &mut self,
645 node_idx: usize,
646 event_type: &str,
647 key: &str,
648 repeat: bool,
649 ) -> (bool, bool) {
650 let (key_code, code, location) = Self::key_attributes(key);
651 let mut extra = alloc::vec![
652 (String::from("key"), Value::str(key)),
653 (String::from("keyCode"), Value::Number(key_code as f64)),
654 (String::from("which"), Value::Number(key_code as f64)),
655 (
656 String::from("charCode"),
657 Value::Number(if event_type == "keypress" {
658 key_code as f64
659 } else {
660 0.0
661 })
662 ),
663 (String::from("code"), Value::str(&code)),
664 (String::from("location"), Value::Number(location as f64)),
665 (String::from("repeat"), Value::Bool(repeat)),
666 ];
667 Self::push_modifier_props(&mut extra);
668 self.dispatch_event_with(node_idx, event_type, &extra)
669 }
670
671 fn key_attributes(key: &str) -> (u32, String, u32) {
674 match key {
676 "Enter" => (13, String::from("Enter"), 0),
677 "Tab" => (9, String::from("Tab"), 0),
678 "Backspace" => (8, String::from("Backspace"), 0),
679 "Escape" => (27, String::from("Escape"), 0),
680 " " => (32, String::from("Space"), 0),
681 "Delete" => (46, String::from("Delete"), 0),
682 "ArrowLeft" => (37, String::from("ArrowLeft"), 0),
683 "ArrowUp" => (38, String::from("ArrowUp"), 0),
684 "ArrowRight" => (39, String::from("ArrowRight"), 0),
685 "ArrowDown" => (40, String::from("ArrowDown"), 0),
686 "Home" => (36, String::from("Home"), 0),
687 "End" => (35, String::from("End"), 0),
688 "PageUp" => (33, String::from("PageUp"), 0),
689 "PageDown" => (34, String::from("PageDown"), 0),
690 "Shift" => (16, String::from("ShiftLeft"), 1),
691 "Control" => (17, String::from("ControlLeft"), 1),
692 "Alt" => (18, String::from("AltLeft"), 1),
693 "Meta" => (91, String::from("MetaLeft"), 1),
694 _ => {
695 let mut chars = key.chars();
697 if let (Some(c), None) = (chars.next(), chars.clone().next()) {
698 let upper = c.to_ascii_uppercase();
699 if upper.is_ascii_alphabetic() {
700 let code = alloc::format!("Key{}", upper);
701 return (upper as u32, code, 0);
702 }
703 if c.is_ascii_digit() {
704 let code = alloc::format!("Digit{}", c);
705 return (c as u32, code, 0);
706 }
707 return (c as u32, String::new(), 0);
708 }
709 (0, String::new(), 0)
710 }
711 }
712 }
713
714 pub fn dispatch_event_with(
720 &mut self,
721 node_idx: usize,
722 event_type: &str,
723 extra: &[(String, Value)],
724 ) -> (bool, bool) {
725 let mut interp = self.new_interp();
730 let r = interp.dispatch_event_in_interp(node_idx, event_type, extra);
731 interp.run_event_loop();
732 self.out.push_str(&interp.out);
733 r
734 }
735
736 pub fn take_pending_reset(&mut self) -> Option<usize> {
739 let g = self.global.clone();
740 let mut gb = g.borrow_mut();
741 let v = match gb.vars.get("__pending_reset") {
742 Some(Value::Number(n)) if *n >= 1.0 => *n as usize - 1,
743 _ => return None,
744 };
745 gb.vars.insert("__pending_reset".into(), Value::Number(0.0));
746 Some(v)
747 }
748
749 pub fn take_pending_nav(&mut self) -> i64 {
752 let history = match self.global.borrow().vars.get("history") {
753 Some(Value::Object(h)) => h.clone(),
754 _ => return 0,
755 };
756 let mut hb = history.borrow_mut();
757 let n = match hb.props.get("_pending_nav") {
758 Some(Value::Number(n)) => *n as i64,
759 _ => 0,
760 };
761 if n != 0 {
762 hb.props.insert("_pending_nav".into(), Value::Number(0.0));
763 }
764 n
765 }
766
767 pub fn take_pending_location(&mut self) -> Option<(String, String)> {
772 let location = match self.global.borrow().vars.get("location") {
773 Some(Value::Object(l)) => l.clone(),
774 _ => return None,
775 };
776 let mut lb = location.borrow_mut();
777 let url = match lb.props.get("_pending_location") {
778 Some(v) => v.to_js_string(),
779 _ => return None,
780 };
781 let mode = lb
782 .props
783 .get("_pending_location_mode")
784 .map(|v| v.to_js_string())
785 .unwrap_or_else(|| String::from("assign"));
786 lb.props.shift_remove("_pending_location");
787 lb.props.shift_remove("_pending_location_mode");
788 Some((url, mode))
789 }
790
791 fn window_listeners(&self, key: &str) -> Vec<Value> {
798 let window = match self.global.borrow().vars.get("window") {
799 Some(Value::Object(w)) => w.clone(),
800 _ => return Vec::new(),
801 };
802 let entries = match window.borrow().props.get(key) {
803 Some(Value::Object(arr)) => match &arr.borrow().kind {
804 ObjKind::Array(items) => items.clone(),
805 _ => Vec::new(),
806 },
807 _ => Vec::new(),
808 };
809 entries
810 .iter()
811 .filter_map(|entry| {
812 let Value::Object(o) = entry else {
813 return Some(entry.clone());
814 };
815 let ObjKind::Array(items) = &o.borrow().kind else {
816 return Some(entry.clone());
817 };
818 if items.get(1).is_some_and(super::dom_bridge::is_signal_aborted) {
819 return None;
820 }
821 items.first().cloned()
822 })
823 .collect()
824 }
825
826 fn fire_window_listeners(
828 &mut self,
829 listeners: &[Value],
830 event_type: &str,
831 extra: &[(String, Value)],
832 ) {
833 if listeners.is_empty() {
834 return;
835 }
836 let ev = Obj::plain();
837 {
838 let mut e = ev.borrow_mut();
839 e.props.insert(String::from("type"), Value::str(event_type));
840 for (k, v) in extra {
841 e.props.insert(k.clone(), v.clone());
842 }
843 }
844 for cb in listeners {
845 let mut interp = self.new_interp();
846 let ctx = alloc::format!("{} listener (window)", event_type);
847 interp.call_listener(cb, Value::Undefined, &[Value::Object(ev.clone())], &ctx);
848 interp.run_event_loop();
849 self.out.push_str(&interp.out);
850 }
851 }
852
853 pub fn fire_popstate(&mut self) {
856 let listeners = self.window_listeners("_popstate_listeners");
857 let state = match self.global.borrow().vars.get("history") {
859 Some(Value::Object(h)) => h
860 .borrow()
861 .props
862 .get("state")
863 .cloned()
864 .unwrap_or(Value::Null),
865 _ => Value::Null,
866 };
867 if !listeners.is_empty() {
868 self.fire_window_listeners(&listeners, "popstate", &[(String::from("state"), state)]);
869 }
870 }
871
872 pub fn has_spa_back(&self) -> bool {
874 let history = match self.global.borrow().vars.get("history") {
875 Some(Value::Object(h)) => h.clone(),
876 _ => return false,
877 };
878 let history_borrow = history.borrow();
879 match history_borrow.props.get("_spa_back_stack") {
880 Some(Value::Object(arr)) => {
881 let arr_borrow = arr.borrow();
882 match &arr_borrow.kind {
883 ObjKind::Array(items) => !items.is_empty(),
884 _ => false,
885 }
886 }
887 _ => false,
888 }
889 }
890
891 pub fn spa_go_back(&mut self) -> Option<String> {
894 let history = match self.global.borrow().vars.get("history") {
895 Some(Value::Object(h)) => h.clone(),
896 _ => return None,
897 };
898 {
900 let cur_url = super::builtins::location_href(&self.global).unwrap_or_default();
901 let cur_state = history.borrow().props.get("state").cloned().unwrap_or(Value::Null);
902 let fwd_stack_val = history.borrow().props.get("_spa_fwd_stack").cloned();
903 let fwd_stack = match fwd_stack_val {
904 Some(Value::Object(arr)) => arr,
905 _ => Obj::array(alloc::vec![]),
906 };
907 let entry = Obj::plain();
908 {
909 let mut eb = entry.borrow_mut();
910 eb.props.insert("url".into(), Value::str(cur_url));
911 eb.props.insert("state".into(), cur_state);
912 }
913 if let ObjKind::Array(items) = &mut fwd_stack.borrow_mut().kind {
914 items.push(Value::Object(entry));
915 }
916 history.borrow_mut().props.insert("_spa_fwd_stack".into(), Value::Object(fwd_stack));
917 }
918 let (prev_url, prev_state) = {
920 let back_stack_val = history.borrow().props.get("_spa_back_stack").cloned();
921 let back_stack = match back_stack_val {
922 Some(Value::Object(arr)) => arr,
923 _ => return None,
924 };
925 let entry = match &mut back_stack.borrow_mut().kind {
926 ObjKind::Array(items) => items.pop(),
927 _ => None,
928 }?;
929 let eb = match entry { Value::Object(o) => o, _ => return None };
930 let url = eb.borrow().props.get("url").cloned().unwrap_or(Value::Undefined).to_js_string();
931 let state = eb.borrow().props.get("state").cloned().unwrap_or(Value::Null);
932 (url, state)
933 };
934 history.borrow_mut().props.insert("state".into(), prev_state);
936 super::builtins::update_location(&self.global, &prev_url);
937 self.base_url = prev_url.to_string();
938 self.fire_popstate();
939 Some(prev_url.to_string())
940 }
941
942 pub fn spa_go_forward(&mut self) -> Option<String> {
944 let history = match self.global.borrow().vars.get("history") {
945 Some(Value::Object(h)) => h.clone(),
946 _ => return None,
947 };
948 {
950 let cur_url = super::builtins::location_href(&self.global).unwrap_or_default();
951 let cur_state = history.borrow().props.get("state").cloned().unwrap_or(Value::Null);
952 let back_stack_val = history.borrow().props.get("_spa_back_stack").cloned();
953 let back_stack = match back_stack_val {
954 Some(Value::Object(arr)) => arr,
955 _ => Obj::array(alloc::vec![]),
956 };
957 let entry = Obj::plain();
958 {
959 let mut eb = entry.borrow_mut();
960 eb.props.insert("url".into(), Value::str(cur_url));
961 eb.props.insert("state".into(), cur_state);
962 }
963 if let ObjKind::Array(items) = &mut back_stack.borrow_mut().kind {
964 items.push(Value::Object(entry));
965 }
966 history.borrow_mut().props.insert("_spa_back_stack".into(), Value::Object(back_stack));
967 }
968 let (next_url, next_state) = {
970 let fwd_stack_val = history.borrow().props.get("_spa_fwd_stack").cloned();
971 let fwd_stack = match fwd_stack_val {
972 Some(Value::Object(arr)) => arr,
973 _ => return None,
974 };
975 let entry = match &mut fwd_stack.borrow_mut().kind {
976 ObjKind::Array(items) => items.pop(),
977 _ => None,
978 }?;
979 let eb = match entry { Value::Object(o) => o, _ => return None };
980 let url = eb.borrow().props.get("url").cloned().unwrap_or(Value::Undefined).to_js_string();
981 let state = eb.borrow().props.get("state").cloned().unwrap_or(Value::Null);
982 (url, state)
983 };
984 history.borrow_mut().props.insert("state".into(), next_state);
985 super::builtins::update_location(&self.global, &next_url);
986 self.base_url = next_url.to_string();
987 self.fire_popstate();
988 Some(next_url.to_string())
989 }
990
991 pub fn has_spa_forward(&self) -> bool {
993 let history = match self.global.borrow().vars.get("history") {
994 Some(Value::Object(h)) => h.clone(),
995 _ => return false,
996 };
997 let history_borrow = history.borrow();
998 match history_borrow.props.get("_spa_fwd_stack") {
999 Some(Value::Object(arr)) => {
1000 let arr_borrow = arr.borrow();
1001 match &arr_borrow.kind {
1002 ObjKind::Array(items) => !items.is_empty(),
1003 _ => false,
1004 }
1005 }
1006 _ => false,
1007 }
1008 }
1009
1010 pub fn fire_scroll(&mut self, scroll_y: i32) {
1013 {
1016 let g = self.global.borrow();
1017 if let Some(Value::Object(w)) = g.vars.get("window") {
1018 let mut wb = w.borrow_mut();
1019 wb.props
1020 .insert("scrollY".into(), Value::Number(scroll_y as f64));
1021 wb.props
1022 .insert("pageYOffset".into(), Value::Number(scroll_y as f64));
1023 }
1024 }
1025 {
1026 let mut g = self.global.borrow_mut();
1027 g.vars
1028 .insert("scrollY".into(), Value::Number(scroll_y as f64));
1029 g.vars
1030 .insert("pageYOffset".into(), Value::Number(scroll_y as f64));
1031 }
1032 let listeners = self.window_listeners("_scroll_listeners");
1033 self.fire_window_listeners(
1034 &listeners,
1035 "scroll",
1036 &[(String::from("scrollY"), Value::Number(scroll_y as f64))],
1037 );
1038 }
1039
1040 pub fn fire_hashchange(&mut self, old_url: &str, new_url: &str) {
1043 let new_hash = match new_url.split_once('#') {
1045 Some((_, frag)) => alloc::format!("#{}", frag),
1046 None => String::new(),
1047 };
1048 {
1049 let g = self.global.borrow();
1050 if let Some(Value::Object(loc)) = g.vars.get("location") {
1051 loc.borrow_mut()
1052 .props
1053 .insert("hash".into(), Value::str(&new_hash));
1054 }
1055 }
1056 let listeners = self.window_listeners("_hashchange_listeners");
1057 self.fire_window_listeners(
1058 &listeners,
1059 "hashchange",
1060 &[
1061 (String::from("oldURL"), Value::str(old_url)),
1062 (String::from("newURL"), Value::str(new_url)),
1063 ],
1064 );
1065 }
1066
1067 pub fn set_viewport_size(&mut self, width: i32, height: i32) {
1071 {
1072 let g = self.global.borrow();
1073 if let Some(Value::Object(w)) = g.vars.get("window") {
1074 let mut wb = w.borrow_mut();
1075 for k in ["innerWidth", "outerWidth"] {
1076 wb.props.insert(k.into(), Value::Number(width as f64));
1077 }
1078 for k in ["innerHeight", "outerHeight"] {
1079 wb.props.insert(k.into(), Value::Number(height as f64));
1080 }
1081 }
1082 }
1083 let mut g = self.global.borrow_mut();
1084 g.vars.insert("innerWidth".into(), Value::Number(width as f64));
1085 g.vars.insert("innerHeight".into(), Value::Number(height as f64));
1086 }
1087
1088 pub fn fire_resize(&mut self, width: i32, height: i32) {
1091 {
1092 let g = self.global.borrow();
1093 if let Some(Value::Object(w)) = g.vars.get("window") {
1094 let mut wb = w.borrow_mut();
1095 wb.props
1096 .insert("innerWidth".into(), Value::Number(width as f64));
1097 wb.props
1098 .insert("innerHeight".into(), Value::Number(height as f64));
1099 }
1100 }
1101 {
1102 let mut g = self.global.borrow_mut();
1104 g.vars
1105 .insert("innerWidth".into(), Value::Number(width as f64));
1106 g.vars
1107 .insert("innerHeight".into(), Value::Number(height as f64));
1108 }
1109 let listeners = self.window_listeners("_resize_listeners");
1110 self.fire_window_listeners(
1111 &listeners,
1112 "resize",
1113 &[
1114 (String::from("innerWidth"), Value::Number(width as f64)),
1115 (String::from("innerHeight"), Value::Number(height as f64)),
1116 ],
1117 );
1118 }
1119}
1120
1121pub struct Interp {
1123 pub steps: u64,
1124 pub max_steps: u64,
1125 pub depth: u32,
1126 pub max_depth: u32,
1127 pub stack_limit: (usize, usize),
1130 pub aborted: bool,
1131 pub out: String,
1132 pub dom: Rc<RefCell<DomBridge>>,
1134 pub microtasks: Rc<RefCell<VecDeque<Job>>>,
1136 gen_replay: Option<GenReplay>,
1138 pub macrotasks: Rc<RefCell<VecDeque<TimerTask>>>,
1140 pub clock_skew: Rc<core::cell::Cell<u64>>,
1142 pub base_url: String,
1144 pub global: Rc<RefCell<Scope>>,
1146 pub io_callbacks: Vec<Value>,
1148 pub modules: ModuleRegistry,
1150 pending_label: Option<String>,
1155 pending_new_target: Option<Value>,
1159 pub(crate) ce_upgrade_target: Option<usize>,
1161 pub(crate) ce_processing: bool,
1163 pub(crate) ce_stack: Vec<(usize, Value)>,
1165 pub(crate) dom_builtin_lookup: bool,
1167 new_target_stack: Vec<Value>,
1170 pub call_names: Vec<String>,
1173 pub tmp_bodies: Vec<Option<Rc<super::ast::FnBody>>>, }
1175
1176type EvalResult = Result<Value, Value>;
1177
1178
1179mod exec;
1184mod iter_gen;
1185mod eval_expr;
1186mod promise_loop;
1187mod operators;
1188mod calls;
1189mod properties;
1190mod dom_props;
1191mod custom_elements;
1192
1193
1194fn aria_attr_name(prop: &str) -> Option<&'static str> {
1200 Some(match prop {
1201 "role" => "role",
1202 "ariaLabel" => "aria-label",
1203 "ariaLabelledBy" => "aria-labelledby",
1204 "ariaDescribedBy" => "aria-describedby",
1205 "ariaHidden" => "aria-hidden",
1206 "ariaExpanded" => "aria-expanded",
1207 "ariaChecked" => "aria-checked",
1208 "ariaSelected" => "aria-selected",
1209 "ariaDisabled" => "aria-disabled",
1210 "ariaPressed" => "aria-pressed",
1211 "ariaCurrent" => "aria-current",
1212 "ariaLive" => "aria-live",
1213 "ariaBusy" => "aria-busy",
1214 "ariaRequired" => "aria-required",
1215 "ariaInvalid" => "aria-invalid",
1216 "ariaValueNow" => "aria-valuenow",
1217 "ariaValueMin" => "aria-valuemin",
1218 "ariaValueMax" => "aria-valuemax",
1219 "ariaValueText" => "aria-valuetext",
1220 "ariaControls" => "aria-controls",
1221 "ariaOwns" => "aria-owns",
1222 "ariaModal" => "aria-modal",
1223 "ariaMultiline" => "aria-multiline",
1224 "ariaMultiSelectable" => "aria-multiselectable",
1225 "ariaOrientation" => "aria-orientation",
1226 "ariaPlaceholder" => "aria-placeholder",
1227 "ariaReadOnly" => "aria-readonly",
1228 "ariaRoleDescription" => "aria-roledescription",
1229 "ariaSort" => "aria-sort",
1230 "ariaAtomic" => "aria-atomic",
1231 "ariaHasPopup" => "aria-haspopup",
1232 "ariaColCount" => "aria-colcount",
1235 "ariaColIndex" => "aria-colindex",
1236 "ariaColSpan" => "aria-colspan",
1237 "ariaRowCount" => "aria-rowcount",
1238 "ariaRowIndex" => "aria-rowindex",
1239 "ariaRowSpan" => "aria-rowspan",
1240 "ariaSetSize" => "aria-setsize",
1241 "ariaPosInSet" => "aria-posinset",
1242 "ariaLevel" => "aria-level",
1243 "ariaKeyShortcuts" => "aria-keyshortcuts",
1244 "ariaAutoComplete" => "aria-autocomplete",
1245 "ariaDetails" => "aria-details",
1246 "ariaErrorMessage" => "aria-errormessage",
1247 "ariaFlowTo" => "aria-flowto",
1248 "ariaRelevant" => "aria-relevant",
1249 _ => return None,
1250 })
1251}
1252
1253fn camel_to_kebab(key: &str) -> String {
1256 let mut s = String::new();
1257 for c in key.chars() {
1258 if c.is_ascii_uppercase() {
1259 s.push('-');
1260 s.push(c.to_ascii_lowercase());
1261 } else {
1262 s.push(c);
1263 }
1264 }
1265 s
1266}
1267
1268pub(crate) fn camel_to_data_attr(key: &str) -> String {
1271 crate::os_lib::dom_dataset::camel_to_data_attr(key)
1272 .unwrap_or_else(|_| alloc::format!("data-{}", key))
1273}
1274
1275pub(crate) enum DomDisp {
1277 Element(usize),
1278 Host(String),
1279}
1280
1281enum CallKind {
1282 User(FunctionData),
1283 Native(NativeFn),
1284 Resolver(Rc<RefCell<PromiseState>>, bool),
1285}
1286
1287pub fn iterable_values(v: &Value) -> Vec<Value> {
1291 match v {
1292 Value::Str(s) => s.chars().map(|c| Value::str(c.to_string())).collect(),
1293 Value::Object(o) => {
1294 let b = o.borrow();
1295 match &b.kind {
1296 ObjKind::Array(items) => items.clone(),
1297 ObjKind::SetObj(items) => items.clone(),
1298 ObjKind::MapObj(entries) => entries
1299 .iter()
1300 .map(|(k, val)| Value::Object(Obj::array(alloc::vec![k.clone(), val.clone()])))
1301 .collect(),
1302 ObjKind::Generator(_) => Vec::new(),
1305 ObjKind::Host(t) if t == "iterator" => {
1311 let items = b.props.get("_items").cloned();
1312 let pos = b
1313 .props
1314 .get("_pos")
1315 .map(|v| v.to_number() as usize)
1316 .unwrap_or(0);
1317 match items {
1318 Some(Value::Object(arr)) => match &arr.borrow().kind {
1319 ObjKind::Array(v) => v.get(pos..).map(|s| s.to_vec()).unwrap_or_default(),
1320 _ => Vec::new(),
1321 },
1322 _ => Vec::new(),
1323 }
1324 }
1325 _ => Vec::new(),
1326 }
1327 }
1328 _ => Vec::new(),
1329 }
1330}
1331
1332fn to_property_key(v: &Value) -> String {
1333 match v {
1334 Value::Number(n) => fmt_number(*n),
1335 Value::Str(s) => (**s).clone(),
1336 _ => v.to_js_string(),
1337 }
1338}
1339
1340fn shift_amount(b: &super::bigint::BigInt) -> Option<i64> {
1343 let f = b.to_f64();
1344 if !f.is_finite() {
1345 return None;
1346 }
1347 if libm::fabs(f) >= 2147483648.0 {
1349 return None;
1350 }
1351 Some(f as i64)
1352}
1353
1354fn to_i32(n: f64) -> i32 {
1355 if !n.is_finite() {
1356 return 0;
1357 }
1358 let m = libm::trunc(n);
1359 (m as i64 as u32) as i32
1360}
1361fn to_u32(n: f64) -> u32 {
1362 to_i32(n) as u32
1363}
1364
1365fn powf(base: f64, exp: f64) -> f64 {
1366 if exp == 0.0 {
1368 return 1.0;
1369 }
1370 libm::pow(base, exp)
1372}
1373
1374fn loose_eq(l: &Value, r: &Value) -> bool {
1375 match (l, r) {
1376 (Value::Null, Value::Undefined) | (Value::Undefined, Value::Null) => true,
1377 (Value::Null, Value::Null) | (Value::Undefined, Value::Undefined) => true,
1378 (Value::Number(a), Value::Number(b)) => a == b,
1379 (Value::Str(a), Value::Str(b)) => a == b,
1380 (Value::Bool(a), Value::Bool(b)) => a == b,
1381 (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
1382 (Value::Object(o), other) | (other, Value::Object(o))
1387 if !matches!(other, Value::Object(_)) =>
1388 {
1389 if matches!(o.borrow().kind, ObjKind::DateObj(_)) {
1390 l.to_number() == r.to_number()
1391 } else {
1392 loose_eq(&Value::str(Value::Object(o.clone()).to_js_string()), other)
1393 }
1394 }
1395 (Value::Number(_), Value::Str(_))
1397 | (Value::Str(_), Value::Number(_))
1398 | (Value::Bool(_), _)
1399 | (_, Value::Bool(_)) => l.to_number() == r.to_number(),
1400 _ => false,
1401 }
1402}
1403
1404fn cmp_f64(a: f64, b: f64) -> Option<core::cmp::Ordering> {
1406 if a.is_nan() || b.is_nan() {
1407 return None;
1408 }
1409 Some(if a < b {
1410 core::cmp::Ordering::Less
1411 } else if a > b {
1412 core::cmp::Ordering::Greater
1413 } else {
1414 core::cmp::Ordering::Equal
1415 })
1416}
1417
1418fn cmp(l: &Value, r: &Value, pred: fn(core::cmp::Ordering) -> bool) -> Value {
1419 if let (Value::Str(a), Value::Str(b)) = (l, r) {
1421 return Value::Bool(pred(a.as_str().cmp(b.as_str())));
1422 }
1423 let a = l.to_number();
1424 let b = r.to_number();
1425 if a.is_nan() || b.is_nan() {
1426 return Value::Bool(false);
1427 }
1428 let ord = if a < b {
1429 core::cmp::Ordering::Less
1430 } else if a > b {
1431 core::cmp::Ordering::Greater
1432 } else {
1433 core::cmp::Ordering::Equal
1434 };
1435 Value::Bool(pred(ord))
1436}
1437
1438fn break_cycles_val(val: Value, visited: &mut alloc::vec::Vec<usize>) {
1439 if let Value::Object(obj) = val {
1440 let ptr = obj.as_ptr() as usize;
1441 if visited.contains(&ptr) {
1442 return;
1443 }
1444 visited.push(ptr);
1445
1446 let (proto, props, accessors, kind) = if let Ok(mut o) = obj.try_borrow_mut() {
1447 let proto = o.proto.take();
1448 let props = core::mem::take(&mut o.props);
1449 let accessors = core::mem::take(&mut o.accessors);
1450 let kind = core::mem::replace(&mut o.kind, super::value::ObjKind::Plain);
1451 o.clear_references();
1452 (proto, props, accessors, kind)
1453 } else {
1454 return;
1455 };
1456
1457 if let Some(p) = proto {
1458 break_cycles_val(Value::Object(p), visited);
1459 }
1460 for (_, v) in props {
1461 break_cycles_val(v, visited);
1462 }
1463 for (_, acc) in accessors {
1464 if let Some(g) = acc.get {
1465 break_cycles_val(g, visited);
1466 }
1467 if let Some(s) = acc.set {
1468 break_cycles_val(s, visited);
1469 }
1470 }
1471 match kind {
1472 super::value::ObjKind::Array(items) => {
1473 for item in items {
1474 break_cycles_val(item, visited);
1475 }
1476 }
1477 super::value::ObjKind::Function(f) => {
1478 if let Some(bt) = f.bound_this {
1479 break_cycles_val(*bt, visited);
1480 }
1481 if let Ok(mut sc) = f.closure.try_borrow_mut() {
1482 sc.parent = None;
1483 let v = core::mem::take(&mut sc.vars);
1484 for (_, val) in v {
1485 break_cycles_val(val, visited);
1486 }
1487 }
1488 }
1489 super::value::ObjKind::MapObj(entries) => {
1490 for (k, v) in entries {
1491 break_cycles_val(k, visited);
1492 break_cycles_val(v, visited);
1493 }
1494 }
1495 super::value::ObjKind::SetObj(items) => {
1496 for item in items {
1497 break_cycles_val(item, visited);
1498 }
1499 }
1500 super::value::ObjKind::PromiseObj(p) => {
1501 if let Ok(mut st) = p.try_borrow_mut() {
1502 break_cycles_val(core::mem::replace(&mut st.value, Value::Undefined), visited);
1503 for r in core::mem::take(&mut st.reactions) {
1504 if let Some(f) = r.on_fulfilled {
1505 break_cycles_val(f, visited);
1506 }
1507 if let Some(rej) = r.on_rejected {
1508 break_cycles_val(rej, visited);
1509 }
1510 }
1511 }
1512 }
1513 super::value::ObjKind::Resolver { state, .. } => {
1514 if let Ok(mut st) = state.try_borrow_mut() {
1515 break_cycles_val(core::mem::replace(&mut st.value, Value::Undefined), visited);
1516 }
1517 }
1518 super::value::ObjKind::Bound { target, bound_this, bound_args } => {
1519 break_cycles_val(target, visited);
1520 break_cycles_val(bound_this, visited);
1521 for a in bound_args {
1522 break_cycles_val(a, visited);
1523 }
1524 }
1525 super::value::ObjKind::Proxy { target, handler } => {
1526 break_cycles_val(Value::Object(target), visited);
1527 break_cycles_val(Value::Object(handler), visited);
1528 }
1529 _ => {}
1530 }
1531 }
1532}
1533
1534impl Drop for JsRuntime {
1535 fn drop(&mut self) {
1536 if let Ok(mut mt) = self.microtasks.try_borrow_mut() {
1537 mt.clear();
1538 }
1539 if let Ok(mut mt) = self.macrotasks.try_borrow_mut() {
1540 mt.clear();
1541 }
1542 if let Ok(mut m) = self.modules.try_borrow_mut() {
1543 m.clear();
1544 }
1545 if let Ok(mut d) = self.dom.try_borrow_mut() {
1546 d.listeners.clear();
1547 }
1548
1549 let vars = if let Ok(mut g) = self.global.try_borrow_mut() {
1550 g.parent = None;
1551 core::mem::take(&mut g.vars)
1552 } else {
1553 return;
1554 };
1555
1556 let mut visited = alloc::vec::Vec::new();
1557 for (_, val) in vars {
1558 break_cycles_val(val, &mut visited);
1559 }
1560 }
1561}
1562