Skip to main content

atmos/os_lib/js/builtins/
worker.rs

1//! WHATWG HTML Living Standard § 10 準拠の Web Worker バインディング
2//!
3//! `Worker` コンストラクタ、`postMessage`、`terminate`、および
4//! メッセージイベントの配送機構を提供する。
5
6use super::*;
7
8static NEXT_WORKER_ID: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(1);
9
10/// `Worker` コンストラクタ (`new Worker(url)`)
11pub(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    // blob: URL の場合、__blob_urls からスクリプトコードを取り出して保持
40    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
60/// ワーカー内 DedicatedWorkerGlobalScope の `self.postMessage(data)`
61pub(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
71/// `worker.postMessage(message, [transfer])`
72pub(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        // スクリプトコードがあり未初期化なら評価
87        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        // ワーカー側のハンドラ(ワーカー内 self.onmessage または _worker_onmessage)の実行
114        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
127/// `worker.terminate()`
128pub(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
139/// ワーカーからメインスレッド側へメッセージを配送し、worker.onmessage および addEventListener('message') を発火する
140pub(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        // 1. worker.onmessage
150        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        // 2. worker.addEventListener('message', ...)
159        let _ = event_target_dispatch_event(it, worker.clone(), &[ev]);
160    }
161}
162
163/// メッセージイベント (`MessageEvent`) オブジェクトを構築
164pub(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}