use serde_json::Value;
use tokio::sync::mpsc;
use crate::{dom::Dom, js_runtime::BrowserJsRuntime};
pub struct WebWorker {
runtime: BrowserJsRuntime,
#[allow(dead_code)] rx: mpsc::Receiver<Value>,
#[allow(dead_code)] tx: mpsc::Sender<Value>,
terminated: bool,
}
impl WebWorker {
pub fn new(script: &str) -> Result<Self, crate::js_runtime::JsError> {
let (tx, rx) = mpsc::channel(100);
let dom = Dom::new();
let mut runtime = BrowserJsRuntime::new(dom);
runtime.execute_script(
r#"
globalThis.self = globalThis;
globalThis._workerMessages = [];
globalThis.postMessage = function(data) {
globalThis._workerMessages.push(JSON.stringify(data));
};
globalThis.onmessage = null;
"#,
)?;
runtime.execute_script(script)?;
Ok(Self {
runtime,
rx,
tx,
terminated: false,
})
}
pub fn post_message(&mut self, data: Value) -> Result<(), crate::js_runtime::JsError> {
if self.terminated {
return Ok(());
}
let json = serde_json::to_string(&data).unwrap_or_default();
self.runtime.execute_script(&format!(
r#"if (typeof onmessage === 'function') {{ onmessage({{ data: JSON.parse('{}') }}); }}"#,
json.replace('\\', "\\\\").replace('\'', "\\'")
))?;
Ok(())
}
pub fn collect_messages(&mut self) -> Result<Vec<Value>, crate::js_runtime::JsError> {
let result = self
.runtime
.execute_script(r#"JSON.stringify(globalThis._workerMessages.splice(0))"#)?;
let messages: Vec<String> = serde_json::from_str(&result).unwrap_or_default();
Ok(messages
.into_iter()
.filter_map(|s| serde_json::from_str(&s).ok())
.collect())
}
pub async fn run_event_loop(&mut self) -> Result<(), crate::js_runtime::JsError> {
if self.terminated {
return Ok(());
}
self.runtime.run_event_loop().await
}
pub fn terminate(&mut self) {
self.terminated = true;
}
pub fn is_terminated(&self) -> bool {
self.terminated
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn create_worker() {
let worker = WebWorker::new("const x = 42;");
assert!(worker.is_ok());
}
#[test]
fn worker_post_message() {
let mut worker = WebWorker::new(
r#"
onmessage = function(e) {
postMessage(e.data * 2);
};
"#,
)
.unwrap();
worker.post_message(json!(21)).unwrap();
let messages = worker.collect_messages().unwrap();
assert_eq!(messages.len(), 1);
assert_eq!(messages[0], json!(42));
}
#[test]
fn worker_no_dom() {
let mut worker = WebWorker::new(
r#"
postMessage(typeof document);
"#,
)
.unwrap();
let messages = worker.collect_messages().unwrap();
assert!(!messages.is_empty());
}
#[test]
fn worker_terminate() {
let mut worker = WebWorker::new("").unwrap();
assert!(!worker.is_terminated());
worker.terminate();
assert!(worker.is_terminated());
}
#[test]
fn worker_multiple_messages() {
let mut worker = WebWorker::new(
r#"
onmessage = function(e) {
postMessage("got: " + e.data);
};
"#,
)
.unwrap();
worker.post_message(json!("hello")).unwrap();
worker.post_message(json!("world")).unwrap();
let messages = worker.collect_messages().unwrap();
assert_eq!(messages.len(), 2);
assert_eq!(messages[0], json!("got: hello"));
assert_eq!(messages[1], json!("got: world"));
}
}