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] = super::events::METHODS;
556
557pub fn instance_call(
558 tag: &str,
559 recv: &Value,
560 method: &str,
561 args: Vec<Value>,
562) -> Result<Value, String> {
563 match tag {
564 "Worker" => worker_call(recv, method, args),
565 "MessagePort" => port_call(recv, method, args),
566 "BroadcastChannel" => broadcast_call(recv, method, args),
567 _ => Err(crate::host::type_error(&format!(
568 "{method} is not a function"
569 ))),
570 }
571}
572
573fn worker_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
575 if EMITTER_METHODS.contains(&method) {
576 return super::events::instance_call(recv, method, args);
577 }
578 match method {
579 "postMessage" => {
580 let json = serialize(&arg0(&args))?;
581 if let Some(id) = u64_prop(recv, "@@wtid") {
582 WORKERS.with(|w| {
583 if let Some(r) = w.borrow().get(&id) {
584 let _ = r.to_worker.send(WorkerMsg::Data(json));
585 }
586 });
587 }
588 Ok(Value::Undef)
589 }
590 "terminate" => {
591 if let Some(id) = u64_prop(recv, "@@wtid") {
594 WORKERS.with(|w| {
595 if let Some(r) = w.borrow().get(&id) {
596 let _ = r.to_worker.send(WorkerMsg::Terminate);
597 }
598 });
599 }
600 Ok(Value::Undef)
601 }
602 "ref" | "unref" => Ok(recv.clone()),
603 _ => Err(crate::host::type_error(&format!(
604 "worker.{method} is not a function"
605 ))),
606 }
607}
608
609fn port_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
611 if let Some(pid) = u64_prop(recv, "@@portid") {
614 return channel_port_call(recv, pid, method, args);
615 }
616 if EMITTER_METHODS.contains(&method) {
617 let r = super::events::instance_call(recv, method, args.clone());
618 if matches!(
620 method,
621 "on" | "addListener" | "prependListener" | "once" | "prependOnceListener"
622 ) {
623 let ev = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
624 if ev == "message" {
625 start_parent_bridge();
626 }
627 }
628 return r;
629 }
630 match method {
631 "postMessage" => {
632 let json = serialize(&arg0(&args))?;
633 WORKER_CTX.with(|c| {
634 if let Some(ctx) = c.borrow().as_ref() {
635 post_to_main(&ctx.main_tx, ctx.self_id, MainEvent::Message(json));
636 }
637 });
638 Ok(Value::Undef)
639 }
640 "start" => {
641 start_parent_bridge();
642 Ok(Value::Undef)
643 }
644 "close" | "ref" | "unref" => Ok(recv.clone()),
645 _ => Err(crate::host::type_error(&format!(
646 "port.{method} is not a function"
647 ))),
648 }
649}
650
651pub fn construct_message_channel(_args: &[Value]) -> Result<Value, String> {
660 let id1 = NEXT_PORT_ID.fetch_add(1, Ordering::SeqCst);
661 let id2 = NEXT_PORT_ID.fetch_add(1, Ordering::SeqCst);
662 let mut e1 = IndexMap::new();
663 e1.insert("@@portid".into(), Value::Float(id1 as f64));
664 let port1 = super::net::new_emitter_object("MessagePort", e1);
665 let mut e2 = IndexMap::new();
666 e2.insert("@@portid".into(), Value::Float(id2 as f64));
667 let port2 = super::net::new_emitter_object("MessagePort", e2);
668
669 CHANNEL_PORTS.with(|m| {
670 let mut m = m.borrow_mut();
671 m.insert(id1, port1.clone());
672 m.insert(id2, port2.clone());
673 });
674 CH_PEER.with(|m| {
675 let mut m = m.borrow_mut();
676 m.insert(id1, id2);
677 m.insert(id2, id1);
678 });
679
680 Ok(with_host(|h| {
681 let mut m = IndexMap::new();
682 m.insert("port1".into(), port1);
683 m.insert("port2".into(), port2);
684 h.new_object(m)
685 }))
686}
687
688fn channel_port_call(
690 recv: &Value,
691 pid: u64,
692 method: &str,
693 args: Vec<Value>,
694) -> Result<Value, String> {
695 if EMITTER_METHODS.contains(&method) {
696 let r = super::events::instance_call(recv, method, args.clone());
697 if matches!(
699 method,
700 "on" | "addListener" | "prependListener" | "once" | "prependOnceListener"
701 ) {
702 let ev = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
703 if ev == "message" {
704 start_channel_port(pid);
705 }
706 }
707 return r;
708 }
709 match method {
710 "postMessage" => {
711 let json = serialize(&arg0(&args))?;
712 channel_post(pid, json);
713 Ok(Value::Undef)
714 }
715 "start" => {
716 start_channel_port(pid);
717 Ok(Value::Undef)
718 }
719 "close" => {
720 CH_STARTED.with(|s| {
721 s.borrow_mut().remove(&pid);
722 });
723 Ok(Value::Undef)
724 }
725 "ref" | "unref" => Ok(recv.clone()),
726 _ => Err(crate::host::type_error(&format!(
727 "port.{method} is not a function"
728 ))),
729 }
730}
731
732fn channel_post(from: u64, json: String) {
735 let Some(peer) = CH_PEER.with(|m| m.borrow().get(&from).copied()) else {
736 return;
737 };
738 CH_QUEUE.with(|q| q.borrow_mut().entry(peer).or_default().push_back(json));
739 if CH_STARTED.with(|s| s.borrow().contains(&peer)) {
740 schedule_channel_delivery(peer);
741 }
742}
743
744fn start_channel_port(pid: u64) {
746 let newly = CH_STARTED.with(|s| s.borrow_mut().insert(pid));
747 if !newly {
748 return;
749 }
750 let pending = CH_QUEUE.with(|q| q.borrow().get(&pid).map_or(0, |d| d.len()));
751 for _ in 0..pending {
752 schedule_channel_delivery(pid);
753 }
754}
755
756fn schedule_channel_delivery(pid: u64) {
759 with_host(|h| h.incr_handle());
760 let io = with_host(|h| h.io_sender());
761 let _ = io.send(Box::new(move || channel_deliver(pid)));
762}
763
764fn channel_deliver(pid: u64) -> Result<(), String> {
768 let json = CH_QUEUE.with(|q| q.borrow_mut().get_mut(&pid).and_then(|d| d.pop_front()));
769 if let Some(json) = json {
770 if let Some(port) = CHANNEL_PORTS.with(|m| m.borrow().get(&pid).cloned()) {
771 match deserialize(&json) {
772 Ok(v) => {
773 if let Err(e) = emit_event(&port, "message", vec![v]) {
774 eprintln!("{e}");
775 }
776 }
777 Err(e) => eprintln!("{e}"),
778 }
779 }
780 }
781 with_host(|h| h.decr_handle());
782 Ok(())
783}
784
785fn receive_message_on_port(port: Option<&Value>) -> Value {
788 let Some(port) = port else {
789 return Value::Undef;
790 };
791 let Some(pid) = u64_prop(port, "@@portid") else {
792 return Value::Undef;
793 };
794 let json = CH_QUEUE.with(|q| q.borrow_mut().get_mut(&pid).and_then(|d| d.pop_front()));
795 match json {
796 Some(j) => match deserialize(&j) {
797 Ok(v) => with_host(|h| {
798 let mut m = IndexMap::new();
799 m.insert("message".into(), v);
800 h.new_object(m)
801 }),
802 Err(_) => Value::Undef,
803 },
804 None => Value::Undef,
805 }
806}
807
808pub fn construct_broadcast_channel(args: &[Value]) -> Result<Value, String> {
819 let name = with_host(|h| h.str_of(&arg0(args)));
820 let id = NEXT_PORT_ID.fetch_add(1, Ordering::SeqCst);
821 let obj = with_host(|h| {
822 let listeners = h.new_array(Vec::new());
823 let mut m = IndexMap::new();
824 m.insert("@@native".into(), h.new_str("BroadcastChannel"));
825 m.insert("@@bcid".into(), Value::Float(id as f64));
826 m.insert("@@bcname".into(), h.new_str(name.clone()));
827 m.insert("@@listeners".into(), listeners);
828 m.insert("@@refed".into(), Value::Bool(true));
829 m.insert("name".into(), h.new_str(name.clone()));
830 m.insert("onmessage".into(), h.null());
831 m.insert("onmessageerror".into(), h.null());
832 h.new_object(m)
833 });
834 BCAST.with(|b| {
835 b.borrow_mut()
836 .entry(name)
837 .or_default()
838 .push((id, obj.clone()))
839 });
840 with_host(|h| h.incr_handle());
841 Ok(obj)
842}
843
844fn broadcast_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
846 match method {
847 "postMessage" => {
848 let json = serialize(&arg0(&args))?;
849 let name = str_prop(recv, "@@bcname");
850 let self_id = u64_prop(recv, "@@bcid");
851 let targets: Vec<Value> = BCAST.with(|b| match b.borrow().get(&name) {
852 Some(list) => list
853 .iter()
854 .filter(|(id, _)| Some(*id) != self_id)
855 .map(|(_, v)| v.clone())
856 .collect(),
857 None => Vec::new(),
858 });
859 for t in targets {
860 schedule_broadcast_delivery(t, json.clone());
861 }
862 Ok(Value::Undef)
863 }
864 "close" => {
865 let name = str_prop(recv, "@@bcname");
866 let self_id = u64_prop(recv, "@@bcid");
867 BCAST.with(|b| {
868 if let Some(list) = b.borrow_mut().get_mut(&name) {
869 list.retain(|(id, _)| Some(*id) != self_id);
870 }
871 });
872 release_broadcast_ref(recv);
873 Ok(Value::Undef)
874 }
875 "addEventListener" => {
876 let ev = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
878 if ev == "message" {
879 if let Some(cb) = args.get(1) {
880 if let Some(arr) = get_prop(recv, "@@listeners") {
881 with_host(|h| {
882 if let Some(JsObj::Array(items)) = h.get_mut(&arr) {
883 items.push(cb.clone());
884 }
885 });
886 }
887 }
888 }
889 Ok(Value::Undef)
890 }
891 "removeEventListener" => Ok(Value::Undef),
892 "ref" => {
893 let refed = matches!(get_prop(recv, "@@refed"), Some(Value::Bool(true)));
894 if !refed {
895 with_host(|h| h.incr_handle());
896 set_bool(recv, "@@refed", true);
897 }
898 Ok(recv.clone())
899 }
900 "unref" => {
901 release_broadcast_ref(recv);
902 Ok(recv.clone())
903 }
904 _ => Err(crate::host::type_error(&format!(
905 "BroadcastChannel.{method} is not a function"
906 ))),
907 }
908}
909
910fn release_broadcast_ref(recv: &Value) {
912 if matches!(get_prop(recv, "@@refed"), Some(Value::Bool(true))) {
913 with_host(|h| h.decr_handle());
914 set_bool(recv, "@@refed", false);
915 }
916}
917
918fn schedule_broadcast_delivery(target: Value, json: String) {
920 with_host(|h| h.incr_handle());
921 let io = with_host(|h| h.io_sender());
922 let _ = io.send(Box::new(move || broadcast_deliver(target, json)));
923}
924
925fn broadcast_deliver(target: Value, json: String) -> Result<(), String> {
929 let value = match deserialize(&json) {
930 Ok(v) => v,
931 Err(e) => {
932 eprintln!("{e}");
933 with_host(|h| h.decr_handle());
934 return Ok(());
935 }
936 };
937 let event = with_host(|h| {
938 let mut m = IndexMap::new();
939 m.insert("data".into(), value);
940 m.insert("type".into(), h.new_str("message"));
941 h.new_object(m)
942 });
943 let onmessage = get_prop(&target, "onmessage");
944 let mut handlers: Vec<Value> = Vec::new();
945 if let Some(cb) = onmessage {
946 if with_host(|h| crate::host::is_callable(h, &cb)) {
947 handlers.push(cb);
948 }
949 }
950 if let Some(arr) = get_prop(&target, "@@listeners") {
951 let listeners: Vec<Value> = with_host(|h| match h.get(&arr) {
952 Some(JsObj::Array(items)) => items.clone(),
953 _ => Vec::new(),
954 });
955 handlers.extend(listeners);
956 }
957 for cb in handlers {
958 if let Err(e) = crate::host::invoke(&cb, vec![event.clone()], None) {
959 eprintln!("{e}");
960 }
961 }
962 with_host(|h| h.decr_handle());
963 Ok(())
964}
965
966fn str_prop(recv: &Value, key: &str) -> String {
968 get_prop(recv, key)
969 .map(|v| with_host(|h| h.str_of(&v)))
970 .unwrap_or_default()
971}
972
973fn set_bool(recv: &Value, key: &str, val: bool) {
975 with_host(|h| {
976 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
977 p.insert(key.to_string(), Value::Bool(val));
978 }
979 });
980}