atmos/os_lib/js/builtins/
worker.rs1use super::*;
7
8static NEXT_WORKER_ID: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(1);
9
10pub(crate) fn worker_ctor(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
12 let script_url = arg(a, 0).to_js_string();
13 if script_url.is_empty() {
14 return Err(it.type_error("Failed to construct 'Worker': 1 argument required, but only 0 present."));
15 }
16
17 let worker_id = NEXT_WORKER_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
18 let obj = match this {
19 Value::Object(o) => o,
20 _ => Obj::plain(),
21 };
22
23 {
24 let mut w = obj.borrow_mut();
25 w.props.insert("_worker_id".into(), Value::Number(worker_id as f64));
26 w.props.insert("_script_url".into(), Value::str(&script_url));
27 w.props.insert("_terminated".into(), Value::Bool(false));
28 w.props.insert("_listeners".into(), Value::Object(Obj::plain()));
29 w.props.insert("onmessage".into(), Value::Null);
30 w.props.insert("onerror".into(), Value::Null);
31 w.props.insert("onmessageerror".into(), Value::Null);
32 w.props.insert("postMessage".into(), nv("Worker.postMessage", worker_post_message));
33 w.props.insert("terminate".into(), nv("Worker.terminate", worker_terminate));
34 w.props.insert("addEventListener".into(), nv("addEventListener", event_target_add_event_listener));
35 w.props.insert("removeEventListener".into(), nv("removeEventListener", event_target_remove_event_listener));
36 w.props.insert("dispatchEvent".into(), nv("dispatchEvent", event_target_dispatch_event));
37 }
38
39 if script_url.starts_with("blob:") {
41 let blob_opt = match it.global.borrow().vars.get("__blob_urls") {
42 Some(Value::Object(r)) => r.borrow().props.get(&script_url).cloned(),
43 _ => None,
44 };
45 if let Some(Value::Object(b)) = blob_opt {
46 if let Some(Value::Object(bytes_arr)) = b.borrow().props.get("_blob_bytes") {
47 if let ObjKind::Array(ref vec) = bytes_arr.borrow().kind {
48 let bytes: Vec<u8> = vec.iter().map(|v| v.to_number() as u8).collect();
49 if let Ok(code) = core::str::from_utf8(&bytes) {
50 obj.borrow_mut().props.insert("_script_code".into(), Value::str(code));
51 }
52 }
53 }
54 }
55 }
56
57 Ok(Value::Object(obj))
58}
59
60pub(crate) fn worker_scope_post_message(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
62 if let Value::Object(scope_obj) = &this {
63 if let Some(worker) = scope_obj.borrow().props.get("_parent_worker").cloned() {
64 let data = structured_clone(it, Value::Undefined, a)?;
65 dispatch_worker_message_to_main(it, &worker, data);
66 }
67 }
68 Ok(Value::Undefined)
69}
70
71pub(crate) fn worker_post_message(it: &mut Interp, this: Value, a: &[Value]) -> Result<Value, Value> {
73 if let Value::Object(w_obj) = &this {
74 let is_terminated = w_obj
75 .borrow()
76 .props
77 .get("_terminated")
78 .map(|v| v.truthy())
79 .unwrap_or(false);
80 if is_terminated {
81 return Ok(Value::Undefined);
82 }
83
84 let data = structured_clone(it, Value::Undefined, a)?;
85
86 let script_code_opt = w_obj.borrow().props.get("_script_code").cloned();
88 let is_inited = w_obj.borrow().props.get("_worker_inited").map(|v| v.truthy()).unwrap_or(false);
89 if !is_inited {
90 if let Some(Value::Str(code_rc)) = script_code_opt {
91 w_obj.borrow_mut().props.insert("_worker_inited".into(), Value::Bool(true));
92 let scope = Obj::plain();
93 {
94 let mut s = scope.borrow_mut();
95 s.props.insert("_parent_worker".into(), this.clone());
96 s.props.insert("postMessage".into(), nv("postMessage", worker_scope_post_message));
97 }
98 let scope_val = Value::Object(scope);
99 it.global.borrow_mut().vars.insert("__worker_current_scope".into(), scope_val.clone());
100 let wrap_code = alloc::format!(
101 "(function(self){{ self.postMessage = __worker_current_scope.postMessage.bind(__worker_current_scope); {}; return self.onmessage; }})(__worker_current_scope)",
102 code_rc
103 );
104 if let Ok(handler) = it.eval_source(&wrap_code) {
105 if !matches!(handler, Value::Null | Value::Undefined) {
106 w_obj.borrow_mut().props.insert("_worker_onmessage".into(), handler);
107 }
108 }
109 it.global.borrow_mut().vars.remove("__worker_current_scope");
110 }
111 }
112
113 let worker_onmessage = w_obj.borrow().props.get("_worker_onmessage").cloned();
115 if let Some(handler) = worker_onmessage {
116 if !matches!(handler, Value::Null | Value::Undefined) {
117 let ev = make_message_event(data.clone(), &this);
118 let _ = it.call_listener(&handler, this.clone(), &[ev], "worker internal onmessage");
119 }
120 }
121
122 return Ok(Value::Undefined);
123 }
124 Err(it.type_error("Worker.postMessage called on non-Worker"))
125}
126
127pub(crate) fn worker_terminate(_it: &mut Interp, this: Value, _a: &[Value]) -> Result<Value, Value> {
129 if let Value::Object(w_obj) = &this {
130 let mut w = w_obj.borrow_mut();
131 w.props.insert("_terminated".into(), Value::Bool(true));
132 w.props.insert("onmessage".into(), Value::Null);
133 w.props.insert("onerror".into(), Value::Null);
134 w.props.insert("onmessageerror".into(), Value::Null);
135 }
136 Ok(Value::Undefined)
137}
138
139pub(crate) fn dispatch_worker_message_to_main(it: &mut Interp, worker: &Value, data: Value) {
141 if let Value::Object(w) = worker {
142 let is_term = w.borrow().props.get("_terminated").map(|v| v.truthy()).unwrap_or(false);
143 if is_term {
144 return;
145 }
146
147 let ev = make_message_event(data, worker);
148
149 let onmessage = w.borrow().props.get("onmessage").cloned();
151 if let Some(handler) = onmessage {
152 if !matches!(handler, Value::Null | Value::Undefined) {
153 let ctx = "worker.onmessage";
154 it.call_listener(&handler, worker.clone(), &[ev.clone()], ctx);
155 }
156 }
157
158 let _ = event_target_dispatch_event(it, worker.clone(), &[ev]);
160 }
161}
162
163pub(crate) fn make_message_event(data: Value, target: &Value) -> Value {
165 let ev = Obj::plain();
166 {
167 let mut e = ev.borrow_mut();
168 e.props.insert("type".into(), Value::str("message"));
169 e.props.insert("data".into(), data);
170 e.props.insert("origin".into(), Value::str(""));
171 e.props.insert("lastEventId".into(), Value::str(""));
172 e.props.insert("source".into(), target.clone());
173 e.props.insert("target".into(), target.clone());
174 e.props.insert("currentTarget".into(), target.clone());
175 }
176 Value::Object(ev)
177}