1use crate::host::{with_host, IoTask, JsObj};
73use fusevm::Value;
74use indexmap::IndexMap;
75use std::cell::RefCell;
76use std::collections::{HashMap, HashSet, VecDeque};
77use std::sync::atomic::{AtomicU64, Ordering};
78use std::sync::mpsc::{Receiver, Sender};
79use std::sync::{Mutex, OnceLock};
80
81pub const METHODS: &[&str] = &[
85 "getEnvironmentData",
86 "setEnvironmentData",
87 "receiveMessageOnPort",
88 "markAsUntransferable",
89 "isMarkedAsUntransferable",
90 "markAsUncloneable",
91 "moveMessagePortToContext",
92];
93
94pub const BROADCAST_CHANNEL_METHODS: &[&str] = &[
96 "postMessage",
97 "close",
98 "ref",
99 "unref",
100 "addEventListener",
101 "removeEventListener",
102];
103
104pub const WORKER_METHODS: &[&str] = &["postMessage", "terminate", "ref", "unref"];
107
108pub const PORT_METHODS: &[&str] = &["postMessage", "close", "start", "ref", "unref"];
111
112static NEXT_THREAD_ID: AtomicU64 = AtomicU64::new(1);
115
116enum WorkerMsg {
119 Data(String),
120 Terminate,
121}
122
123enum MainEvent {
126 Online,
127 Message(String),
128 Error(String),
129 Exit(i32),
130}
131
132struct WorkerRec {
134 emitter: Value,
136 to_worker: Sender<WorkerMsg>,
138}
139
140thread_local! {
141 static WORKERS: RefCell<HashMap<u64, WorkerRec>> = RefCell::new(HashMap::new());
144}
145
146struct WorkerCtx {
150 thread_id: u64,
151 worker_data_json: String,
154 main_tx: Sender<IoTask>,
156 self_id: u64,
158 rx: Option<Receiver<WorkerMsg>>,
160 bridge_started: bool,
162}
163
164thread_local! {
165 static WORKER_CTX: RefCell<Option<WorkerCtx>> = const { RefCell::new(None) };
166 static PARENT_PORT: RefCell<Option<Value>> = const { RefCell::new(None) };
169
170 static CHANNEL_PORTS: RefCell<HashMap<u64, Value>> = RefCell::new(HashMap::new());
173 static CH_PEER: RefCell<HashMap<u64, u64>> = RefCell::new(HashMap::new());
175 static CH_QUEUE: RefCell<HashMap<u64, VecDeque<String>>> = RefCell::new(HashMap::new());
178 static CH_STARTED: RefCell<HashSet<u64>> = RefCell::new(HashSet::new());
181
182 static BCAST: RefCell<HashMap<String, Vec<(u64, Value)>>> = RefCell::new(HashMap::new());
185
186 static UNTRANSFERABLE: RefCell<HashSet<u32>> = RefCell::new(HashSet::new());
188 static UNCLONEABLE: RefCell<HashSet<u32>> = RefCell::new(HashSet::new());
189}
190
191fn env_data() -> &'static Mutex<HashMap<String, String>> {
195 static ENV_DATA: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();
196 ENV_DATA.get_or_init(|| Mutex::new(HashMap::new()))
197}
198
199static NEXT_PORT_ID: AtomicU64 = AtomicU64::new(1);
202
203fn serialize(v: &Value) -> Result<String, String> {
209 let arr = with_host(|h| h.new_array(vec![v.clone()]));
210 let json = crate::builtins::call_builtin_function("JSON.stringify", vec![arr])?;
211 Ok(with_host(|h| h.str_of(&json)))
212}
213
214fn deserialize(json: &str) -> Result<Value, String> {
217 let sv = with_host(|h| h.new_str(json.to_string()));
218 let arr = crate::builtins::call_builtin_function("JSON.parse", vec![sv])?;
219 Ok(with_host(|h| match h.get(&arr) {
220 Some(JsObj::Array(items)) => items.first().cloned().unwrap_or(Value::Undef),
221 _ => Value::Undef,
222 }))
223}
224
225fn arg0(args: &[Value]) -> Value {
228 args.first().cloned().unwrap_or(Value::Undef)
229}
230
231fn get_prop(recv: &Value, key: &str) -> Option<Value> {
232 with_host(|h| match h.get(recv) {
233 Some(JsObj::Object(p)) => p.get(key).cloned(),
234 _ => None,
235 })
236}
237
238fn u64_prop(recv: &Value, key: &str) -> Option<u64> {
239 get_prop(recv, key).map(|v| with_host(|h| h.to_number(&v)) as u64)
240}
241
242fn emit_event(emitter: &Value, name: &str, mut args: Vec<Value>) -> Result<(), String> {
245 let mut a = vec![with_host(|h| h.new_str(name))];
246 a.append(&mut args);
247 super::events::instance_call(emitter, "emit", a).map(|_| ())
248}
249
250fn is_worker_thread() -> bool {
253 WORKER_CTX.with(|c| c.borrow().is_some())
254}
255
256fn current_thread_id() -> u64 {
257 WORKER_CTX.with(|c| c.borrow().as_ref().map(|x| x.thread_id).unwrap_or(0))
258}
259
260fn current_worker_data() -> Value {
263 let json = WORKER_CTX.with(|c| c.borrow().as_ref().map(|x| x.worker_data_json.clone()));
264 match json {
265 Some(j) => deserialize(&j).unwrap_or(Value::Undef),
266 None => Value::Undef,
267 }
268}
269
270fn ensure_parent_port() -> Value {
273 if let Some(p) = PARENT_PORT.with(|p| p.borrow().clone()) {
274 return p;
275 }
276 let port = super::net::new_emitter_object("MessagePort", IndexMap::new());
277 PARENT_PORT.with(|p| *p.borrow_mut() = Some(port.clone()));
278 port
279}
280
281pub fn constant(name: &str) -> Option<Value> {
288 match name {
289 "isMainThread" => Some(Value::Bool(!is_worker_thread())),
290 "threadId" => Some(Value::Float(current_thread_id() as f64)),
291 "parentPort" => Some(if is_worker_thread() {
292 ensure_parent_port()
293 } else {
294 with_host(|h| h.null())
296 }),
297 "workerData" => Some(current_worker_data()),
298 "Worker" | "MessageChannel" | "BroadcastChannel" => {
299 Some(with_host(|h| h.alloc(JsObj::Builtin(name.into()))))
300 }
301 _ => None,
302 }
303}
304
305pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
308 Some(match method {
309 "setEnvironmentData" => {
310 let key = super::arg_str(args, 0);
311 match args.get(1) {
312 Some(v) if !matches!(v, Value::Undef) => match serialize(v) {
314 Ok(json) => {
315 if let Ok(mut m) = env_data().lock() {
316 m.insert(key, json);
317 }
318 Ok(Value::Undef)
319 }
320 Err(e) => Err(e),
321 },
322 _ => {
323 if let Ok(mut m) = env_data().lock() {
324 m.remove(&key);
325 }
326 Ok(Value::Undef)
327 }
328 }
329 }
330 "getEnvironmentData" => {
331 let key = super::arg_str(args, 0);
332 let json = env_data().lock().ok().and_then(|m| m.get(&key).cloned());
333 match json {
334 Some(j) => deserialize(&j),
335 None => Ok(Value::Undef),
336 }
337 }
338 "receiveMessageOnPort" => Ok(receive_message_on_port(args.first())),
339 "markAsUntransferable" => {
343 if let Some(Value::Obj(id)) = args.first() {
344 UNTRANSFERABLE.with(|s| s.borrow_mut().insert(*id));
345 }
346 Ok(Value::Undef)
347 }
348 "isMarkedAsUntransferable" => Ok(Value::Bool(matches!(
349 args.first(),
350 Some(Value::Obj(id)) if UNTRANSFERABLE.with(|s| s.borrow().contains(id))
351 ))),
352 "markAsUncloneable" => {
353 if let Some(Value::Obj(id)) = args.first() {
354 UNCLONEABLE.with(|s| s.borrow_mut().insert(*id));
355 }
356 Ok(Value::Undef)
357 }
358 "moveMessagePortToContext" => Ok(arg0(args)),
361 _ => return None,
362 })
363}
364
365pub fn construct_worker(args: &[Value]) -> Result<Value, String> {
371 let filename = with_host(|h| h.str_of(&arg0(args)));
372 let opts = args.get(1).cloned();
373 let is_eval = opts
374 .as_ref()
375 .and_then(|o| get_prop(o, "eval"))
376 .map(|v| with_host(|h| h.truthy(&v)))
377 .unwrap_or(false);
378 let worker_data_json = match opts.as_ref().and_then(|o| get_prop(o, "workerData")) {
380 Some(v) => serialize(&v)?,
381 None => serialize(&Value::Undef)?, };
383
384 let id = NEXT_THREAD_ID.fetch_add(1, Ordering::SeqCst);
385 let (to_worker_tx, to_worker_rx) = std::sync::mpsc::channel::<WorkerMsg>();
386 let main_tx = with_host(|h| h.io_sender());
387
388 let mut extra = IndexMap::new();
389 extra.insert("@@wtid".into(), Value::Float(id as f64));
390 extra.insert("threadId".into(), Value::Float(id as f64));
391 let emitter = super::net::new_emitter_object("Worker", extra);
392
393 WORKERS.with(|w| {
394 w.borrow_mut().insert(
395 id,
396 WorkerRec {
397 emitter: emitter.clone(),
398 to_worker: to_worker_tx,
399 },
400 );
401 });
402 with_host(|h| h.incr_handle());
404
405 let spawn_tx = main_tx.clone();
406 std::thread::spawn(move || {
407 worker_thread_main(
408 id,
409 filename,
410 is_eval,
411 worker_data_json,
412 spawn_tx,
413 to_worker_rx,
414 );
415 });
416
417 Ok(emitter)
418}
419
420fn worker_thread_main(
423 id: u64,
424 filename: String,
425 is_eval: bool,
426 worker_data_json: String,
427 main_tx: Sender<IoTask>,
428 rx: Receiver<WorkerMsg>,
429) {
430 WORKER_CTX.with(|c| {
431 *c.borrow_mut() = Some(WorkerCtx {
432 thread_id: id,
433 worker_data_json,
434 main_tx: main_tx.clone(),
435 self_id: id,
436 rx: Some(rx),
437 bridge_started: false,
438 });
439 });
440
441 post_to_main(&main_tx, id, MainEvent::Online);
443
444 let outcome = if is_eval {
449 crate::eval_str(&filename)
450 } else {
451 crate::eval_file(&filename)
452 };
453
454 match outcome {
455 Ok(_) => post_to_main(&main_tx, id, MainEvent::Exit(0)),
456 Err(e) => {
457 post_to_main(&main_tx, id, MainEvent::Error(e));
458 post_to_main(&main_tx, id, MainEvent::Exit(1));
459 }
460 }
461}
462
463fn post_to_main(main_tx: &Sender<IoTask>, id: u64, ev: MainEvent) {
467 let _ = main_tx.send(Box::new(move || dispatch_main(id, ev)));
468}
469
470fn dispatch_main(id: u64, ev: MainEvent) -> Result<(), String> {
472 let emitter = WORKERS.with(|w| w.borrow().get(&id).map(|r| r.emitter.clone()));
473 let Some(emitter) = emitter else {
474 return Ok(());
475 };
476 match ev {
477 MainEvent::Online => emit_event(&emitter, "online", vec![]),
478 MainEvent::Message(json) => {
479 let v = deserialize(&json)?;
480 emit_event(&emitter, "message", vec![v])
481 }
482 MainEvent::Error(msg) => {
483 let err =
484 crate::builtins::construct_builtin("Error", vec![with_host(|h| h.new_str(msg))])?;
485 emit_event(&emitter, "error", vec![err])
486 }
487 MainEvent::Exit(code) => {
488 emit_event(&emitter, "exit", vec![Value::Float(code as f64)])?;
489 WORKERS.with(|w| {
490 w.borrow_mut().remove(&id);
491 });
492 with_host(|h| h.decr_handle());
493 let _ = with_host(|h| h.io_sender()).send(Box::new(|| Ok(())));
495 Ok(())
496 }
497 }
498}
499
500fn start_parent_bridge() {
507 let worker_io: Option<Sender<IoTask>> = WORKER_CTX.with(|c| {
509 let mut cb = c.borrow_mut();
510 let ctx = cb.as_mut()?;
511 if ctx.bridge_started {
512 return None;
513 }
514 let rx = ctx.rx.take()?;
515 ctx.bridge_started = true;
516 let io = with_host(|h| h.io_sender());
517 with_host(|h| h.incr_handle());
518 let io_for_thread = io.clone();
520 std::thread::spawn(move || {
521 while let Ok(msg) = rx.recv() {
522 match msg {
523 WorkerMsg::Data(json) => {
524 let _ = io_for_thread.send(Box::new(move || parent_deliver(json)));
525 }
526 WorkerMsg::Terminate => {
527 let _ = io_for_thread.send(Box::new(|| {
529 with_host(|h| h.decr_handle());
530 Ok(())
531 }));
532 break;
533 }
534 }
535 }
536 });
537 Some(io)
538 });
539 let _ = worker_io;
540}
541
542fn parent_deliver(json: String) -> Result<(), String> {
546 let port = ensure_parent_port();
547 let v = deserialize(&json)?;
548 emit_event(&port, "message", vec![v])
549}
550
551const EMITTER_METHODS: &[&str] = &[
555 "on",
556 "addListener",
557 "prependListener",
558 "once",
559 "prependOnceListener",
560 "emit",
561 "removeListener",
562 "off",
563 "removeAllListeners",
564 "listenerCount",
565 "listeners",
566 "eventNames",
567 "setMaxListeners",
568 "getMaxListeners",
569];
570
571pub fn instance_call(
572 tag: &str,
573 recv: &Value,
574 method: &str,
575 args: Vec<Value>,
576) -> Result<Value, String> {
577 match tag {
578 "Worker" => worker_call(recv, method, args),
579 "MessagePort" => port_call(recv, method, args),
580 "BroadcastChannel" => broadcast_call(recv, method, args),
581 _ => Err(crate::host::type_error(&format!(
582 "{method} is not a function"
583 ))),
584 }
585}
586
587fn worker_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
589 if EMITTER_METHODS.contains(&method) {
590 return super::events::instance_call(recv, method, args);
591 }
592 match method {
593 "postMessage" => {
594 let json = serialize(&arg0(&args))?;
595 if let Some(id) = u64_prop(recv, "@@wtid") {
596 WORKERS.with(|w| {
597 if let Some(r) = w.borrow().get(&id) {
598 let _ = r.to_worker.send(WorkerMsg::Data(json));
599 }
600 });
601 }
602 Ok(Value::Undef)
603 }
604 "terminate" => {
605 if let Some(id) = u64_prop(recv, "@@wtid") {
608 WORKERS.with(|w| {
609 if let Some(r) = w.borrow().get(&id) {
610 let _ = r.to_worker.send(WorkerMsg::Terminate);
611 }
612 });
613 }
614 Ok(Value::Undef)
615 }
616 "ref" | "unref" => Ok(recv.clone()),
617 _ => Err(crate::host::type_error(&format!(
618 "worker.{method} is not a function"
619 ))),
620 }
621}
622
623fn port_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
625 if let Some(pid) = u64_prop(recv, "@@portid") {
628 return channel_port_call(recv, pid, method, args);
629 }
630 if EMITTER_METHODS.contains(&method) {
631 let r = super::events::instance_call(recv, method, args.clone());
632 if matches!(
634 method,
635 "on" | "addListener" | "prependListener" | "once" | "prependOnceListener"
636 ) {
637 let ev = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
638 if ev == "message" {
639 start_parent_bridge();
640 }
641 }
642 return r;
643 }
644 match method {
645 "postMessage" => {
646 let json = serialize(&arg0(&args))?;
647 WORKER_CTX.with(|c| {
648 if let Some(ctx) = c.borrow().as_ref() {
649 post_to_main(&ctx.main_tx, ctx.self_id, MainEvent::Message(json));
650 }
651 });
652 Ok(Value::Undef)
653 }
654 "start" => {
655 start_parent_bridge();
656 Ok(Value::Undef)
657 }
658 "close" | "ref" | "unref" => Ok(recv.clone()),
659 _ => Err(crate::host::type_error(&format!(
660 "port.{method} is not a function"
661 ))),
662 }
663}
664
665pub fn construct_message_channel(_args: &[Value]) -> Result<Value, String> {
674 let id1 = NEXT_PORT_ID.fetch_add(1, Ordering::SeqCst);
675 let id2 = NEXT_PORT_ID.fetch_add(1, Ordering::SeqCst);
676 let mut e1 = IndexMap::new();
677 e1.insert("@@portid".into(), Value::Float(id1 as f64));
678 let port1 = super::net::new_emitter_object("MessagePort", e1);
679 let mut e2 = IndexMap::new();
680 e2.insert("@@portid".into(), Value::Float(id2 as f64));
681 let port2 = super::net::new_emitter_object("MessagePort", e2);
682
683 CHANNEL_PORTS.with(|m| {
684 let mut m = m.borrow_mut();
685 m.insert(id1, port1.clone());
686 m.insert(id2, port2.clone());
687 });
688 CH_PEER.with(|m| {
689 let mut m = m.borrow_mut();
690 m.insert(id1, id2);
691 m.insert(id2, id1);
692 });
693
694 Ok(with_host(|h| {
695 let mut m = IndexMap::new();
696 m.insert("port1".into(), port1);
697 m.insert("port2".into(), port2);
698 h.new_object(m)
699 }))
700}
701
702fn channel_port_call(
704 recv: &Value,
705 pid: u64,
706 method: &str,
707 args: Vec<Value>,
708) -> Result<Value, String> {
709 if EMITTER_METHODS.contains(&method) {
710 let r = super::events::instance_call(recv, method, args.clone());
711 if matches!(
713 method,
714 "on" | "addListener" | "prependListener" | "once" | "prependOnceListener"
715 ) {
716 let ev = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
717 if ev == "message" {
718 start_channel_port(pid);
719 }
720 }
721 return r;
722 }
723 match method {
724 "postMessage" => {
725 let json = serialize(&arg0(&args))?;
726 channel_post(pid, json);
727 Ok(Value::Undef)
728 }
729 "start" => {
730 start_channel_port(pid);
731 Ok(Value::Undef)
732 }
733 "close" => {
734 CH_STARTED.with(|s| {
735 s.borrow_mut().remove(&pid);
736 });
737 Ok(Value::Undef)
738 }
739 "ref" | "unref" => Ok(recv.clone()),
740 _ => Err(crate::host::type_error(&format!(
741 "port.{method} is not a function"
742 ))),
743 }
744}
745
746fn channel_post(from: u64, json: String) {
749 let Some(peer) = CH_PEER.with(|m| m.borrow().get(&from).copied()) else {
750 return;
751 };
752 CH_QUEUE.with(|q| q.borrow_mut().entry(peer).or_default().push_back(json));
753 if CH_STARTED.with(|s| s.borrow().contains(&peer)) {
754 schedule_channel_delivery(peer);
755 }
756}
757
758fn start_channel_port(pid: u64) {
760 let newly = CH_STARTED.with(|s| s.borrow_mut().insert(pid));
761 if !newly {
762 return;
763 }
764 let pending = CH_QUEUE.with(|q| q.borrow().get(&pid).map_or(0, |d| d.len()));
765 for _ in 0..pending {
766 schedule_channel_delivery(pid);
767 }
768}
769
770fn schedule_channel_delivery(pid: u64) {
773 with_host(|h| h.incr_handle());
774 let io = with_host(|h| h.io_sender());
775 let _ = io.send(Box::new(move || channel_deliver(pid)));
776}
777
778fn channel_deliver(pid: u64) -> Result<(), String> {
782 let json = CH_QUEUE.with(|q| q.borrow_mut().get_mut(&pid).and_then(|d| d.pop_front()));
783 if let Some(json) = json {
784 if let Some(port) = CHANNEL_PORTS.with(|m| m.borrow().get(&pid).cloned()) {
785 match deserialize(&json) {
786 Ok(v) => {
787 if let Err(e) = emit_event(&port, "message", vec![v]) {
788 eprintln!("{e}");
789 }
790 }
791 Err(e) => eprintln!("{e}"),
792 }
793 }
794 }
795 with_host(|h| h.decr_handle());
796 Ok(())
797}
798
799fn receive_message_on_port(port: Option<&Value>) -> Value {
802 let Some(port) = port else {
803 return Value::Undef;
804 };
805 let Some(pid) = u64_prop(port, "@@portid") else {
806 return Value::Undef;
807 };
808 let json = CH_QUEUE.with(|q| q.borrow_mut().get_mut(&pid).and_then(|d| d.pop_front()));
809 match json {
810 Some(j) => match deserialize(&j) {
811 Ok(v) => with_host(|h| {
812 let mut m = IndexMap::new();
813 m.insert("message".into(), v);
814 h.new_object(m)
815 }),
816 Err(_) => Value::Undef,
817 },
818 None => Value::Undef,
819 }
820}
821
822pub fn construct_broadcast_channel(args: &[Value]) -> Result<Value, String> {
833 let name = with_host(|h| h.str_of(&arg0(args)));
834 let id = NEXT_PORT_ID.fetch_add(1, Ordering::SeqCst);
835 let obj = with_host(|h| {
836 let listeners = h.new_array(Vec::new());
837 let mut m = IndexMap::new();
838 m.insert("@@native".into(), h.new_str("BroadcastChannel"));
839 m.insert("@@bcid".into(), Value::Float(id as f64));
840 m.insert("@@bcname".into(), h.new_str(name.clone()));
841 m.insert("@@listeners".into(), listeners);
842 m.insert("@@refed".into(), Value::Bool(true));
843 m.insert("name".into(), h.new_str(name.clone()));
844 m.insert("onmessage".into(), h.null());
845 m.insert("onmessageerror".into(), h.null());
846 h.new_object(m)
847 });
848 BCAST.with(|b| {
849 b.borrow_mut()
850 .entry(name)
851 .or_default()
852 .push((id, obj.clone()))
853 });
854 with_host(|h| h.incr_handle());
855 Ok(obj)
856}
857
858fn broadcast_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
860 match method {
861 "postMessage" => {
862 let json = serialize(&arg0(&args))?;
863 let name = str_prop(recv, "@@bcname");
864 let self_id = u64_prop(recv, "@@bcid");
865 let targets: Vec<Value> = BCAST.with(|b| match b.borrow().get(&name) {
866 Some(list) => list
867 .iter()
868 .filter(|(id, _)| Some(*id) != self_id)
869 .map(|(_, v)| v.clone())
870 .collect(),
871 None => Vec::new(),
872 });
873 for t in targets {
874 schedule_broadcast_delivery(t, json.clone());
875 }
876 Ok(Value::Undef)
877 }
878 "close" => {
879 let name = str_prop(recv, "@@bcname");
880 let self_id = u64_prop(recv, "@@bcid");
881 BCAST.with(|b| {
882 if let Some(list) = b.borrow_mut().get_mut(&name) {
883 list.retain(|(id, _)| Some(*id) != self_id);
884 }
885 });
886 release_broadcast_ref(recv);
887 Ok(Value::Undef)
888 }
889 "addEventListener" => {
890 let ev = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
892 if ev == "message" {
893 if let Some(cb) = args.get(1) {
894 if let Some(arr) = get_prop(recv, "@@listeners") {
895 with_host(|h| {
896 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
897 items.push(cb.clone());
898 }
899 });
900 }
901 }
902 }
903 Ok(Value::Undef)
904 }
905 "removeEventListener" => Ok(Value::Undef),
906 "ref" => {
907 let refed = matches!(get_prop(recv, "@@refed"), Some(Value::Bool(true)));
908 if !refed {
909 with_host(|h| h.incr_handle());
910 set_bool(recv, "@@refed", true);
911 }
912 Ok(recv.clone())
913 }
914 "unref" => {
915 release_broadcast_ref(recv);
916 Ok(recv.clone())
917 }
918 _ => Err(crate::host::type_error(&format!(
919 "BroadcastChannel.{method} is not a function"
920 ))),
921 }
922}
923
924fn release_broadcast_ref(recv: &Value) {
926 if matches!(get_prop(recv, "@@refed"), Some(Value::Bool(true))) {
927 with_host(|h| h.decr_handle());
928 set_bool(recv, "@@refed", false);
929 }
930}
931
932fn schedule_broadcast_delivery(target: Value, json: String) {
934 with_host(|h| h.incr_handle());
935 let io = with_host(|h| h.io_sender());
936 let _ = io.send(Box::new(move || broadcast_deliver(target, json)));
937}
938
939fn broadcast_deliver(target: Value, json: String) -> Result<(), String> {
943 let value = match deserialize(&json) {
944 Ok(v) => v,
945 Err(e) => {
946 eprintln!("{e}");
947 with_host(|h| h.decr_handle());
948 return Ok(());
949 }
950 };
951 let event = with_host(|h| {
952 let mut m = IndexMap::new();
953 m.insert("data".into(), value);
954 m.insert("type".into(), h.new_str("message"));
955 h.new_object(m)
956 });
957 let onmessage = get_prop(&target, "onmessage");
958 let mut handlers: Vec<Value> = Vec::new();
959 if let Some(cb) = onmessage {
960 if with_host(|h| crate::host::is_callable(h, &cb)) {
961 handlers.push(cb);
962 }
963 }
964 if let Some(arr) = get_prop(&target, "@@listeners") {
965 let listeners: Vec<Value> = with_host(|h| match h.get(&arr) {
966 Some(JsObj::Array(items)) => items.clone(),
967 _ => Vec::new(),
968 });
969 handlers.extend(listeners);
970 }
971 for cb in handlers {
972 if let Err(e) = crate::host::invoke(&cb, vec![event.clone()], None) {
973 eprintln!("{e}");
974 }
975 }
976 with_host(|h| h.decr_handle());
977 Ok(())
978}
979
980fn str_prop(recv: &Value, key: &str) -> String {
982 get_prop(recv, key)
983 .map(|v| with_host(|h| h.str_of(&v)))
984 .unwrap_or_default()
985}
986
987fn set_bool(recv: &Value, key: &str, val: bool) {
989 with_host(|h| {
990 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
991 p.insert(key.to_string(), Value::Bool(val));
992 }
993 });
994}