mirror of
https://github.com/tlsnotary/tlsn-utils.git
synced 2026-01-08 22:48:09 -05:00
refactor(web-spawn): use snippets (#52)
This commit is contained in:
@@ -6,6 +6,10 @@ description = "`std` spawn replacement for WASM in the browser."
|
||||
repository = "https://github.com/tlsnotary/tlsn-utils"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
no-bundler = []
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
wasm-bindgen-futures = { version = "0.4" }
|
||||
|
||||
69
web-spawn/js/spawn.js
Normal file
69
web-spawn/js/spawn.js
Normal file
@@ -0,0 +1,69 @@
|
||||
function registerMessageListener(target, type, callback) {
|
||||
const listener = async (event) => {
|
||||
const message = event.data;
|
||||
if (message && message.type === type) {
|
||||
await callback(message.data);
|
||||
}
|
||||
};
|
||||
|
||||
target.addEventListener('message', listener);
|
||||
}
|
||||
|
||||
// Register listener for the start spawner message.
|
||||
registerMessageListener(self, 'web_spawn_start_spawner', async (data) => {
|
||||
const workerUrl = new URL(
|
||||
'./spawn.js',
|
||||
import.meta.url
|
||||
);
|
||||
const [module, memory, spawnerPtr] = data;
|
||||
const pkg = await import('../../..');
|
||||
await pkg.default({ module, memory });
|
||||
|
||||
const spawner = pkg.web_spawn_recover_spawner(spawnerPtr);
|
||||
postMessage('web_spawn_spawner_ready');
|
||||
await spawner.run(workerUrl.toString());
|
||||
|
||||
close();
|
||||
});
|
||||
|
||||
// Register listener for the start worker message.
|
||||
registerMessageListener(self, 'web_spawn_start_worker', async (data) => {
|
||||
const [module, memory, workerPtr] = data;
|
||||
|
||||
const pkg = await import('../../..');
|
||||
await pkg.default({ module, memory });
|
||||
|
||||
pkg.web_spawn_start_worker(workerPtr);
|
||||
|
||||
close();
|
||||
});
|
||||
|
||||
/// Starts the spawner in a new worker.
|
||||
export async function startSpawnerWorker(module, memory, spawner) {
|
||||
const workerUrl = new URL(
|
||||
'./spawn.js',
|
||||
import.meta.url
|
||||
);
|
||||
const worker = new Worker(
|
||||
workerUrl,
|
||||
{
|
||||
name: 'web-spawn-spawner',
|
||||
type: 'module'
|
||||
}
|
||||
);
|
||||
|
||||
const data = [module, memory, spawner.intoRaw()];
|
||||
worker.postMessage({
|
||||
type: 'web_spawn_start_spawner',
|
||||
data: data
|
||||
})
|
||||
|
||||
await new Promise(resolve => {
|
||||
worker.addEventListener('message', function handler(event) {
|
||||
if (event.data === 'web_spawn_spawner_ready') {
|
||||
worker.removeEventListener('message', handler);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
62
web-spawn/js/spawn.no-bundler.js
Normal file
62
web-spawn/js/spawn.no-bundler.js
Normal file
@@ -0,0 +1,62 @@
|
||||
function registerMessageListener(target, type, callback) {
|
||||
const listener = async (event) => {
|
||||
const message = event.data;
|
||||
if (message && message.type === type) {
|
||||
await callback(message.data);
|
||||
}
|
||||
};
|
||||
|
||||
target.addEventListener('message', listener);
|
||||
}
|
||||
|
||||
// Register listener for the start spawner message.
|
||||
registerMessageListener(self, 'web_spawn_start_spawner', async (data) => {
|
||||
const [module, memory, workerUrl, wasmUrl, spawner] = data;
|
||||
const wasm = await import(`${wasmUrl}`);
|
||||
|
||||
wasm.initSync({ module, memory });
|
||||
postMessage('web_spawn_spawner_ready');
|
||||
await wasm.web_spawn_recover_spawner(spawner).run(workerUrl);
|
||||
|
||||
URL.revokeObjectURL(workerUrl);
|
||||
|
||||
close();
|
||||
});
|
||||
|
||||
// Register listener for the start worker message.
|
||||
registerMessageListener(self, 'web_spawn_start_worker', async (data) => {
|
||||
const [module, memory, wasmUrl, worker] = data;
|
||||
const wasm = await import(`${wasmUrl}`);
|
||||
|
||||
wasm.initSync({ module, memory });
|
||||
wasm.web_spawn_start_worker(worker);
|
||||
|
||||
close();
|
||||
});
|
||||
|
||||
/// Starts the spawner in a new worker.
|
||||
export async function startSpawnerWorker(module, memory, spawner) {
|
||||
let workerUrl = import.meta.url;
|
||||
let scriptBlob = await fetch(`${workerUrl}`).then(r => r.blob());
|
||||
workerUrl = URL.createObjectURL(scriptBlob);
|
||||
|
||||
const worker = new Worker(workerUrl, {
|
||||
name: 'web-spawn-spawner',
|
||||
type: 'module'
|
||||
});
|
||||
|
||||
const wasmUrl = spawner.getUrl();
|
||||
worker.postMessage({
|
||||
type: 'web_spawn_start_spawner',
|
||||
data: [module, memory, workerUrl, wasmUrl, spawner.intoRaw()]
|
||||
});
|
||||
|
||||
await new Promise(resolve => {
|
||||
worker.addEventListener('message', function handler(event) {
|
||||
if (event.data === 'web_spawn_spawner_ready') {
|
||||
worker.removeEventListener('message', handler);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test from main browser thread
|
||||
WASM_BINDGEN_USE_BROWSER=1 wasm-pack test --headless --chrome --firefox
|
||||
WASM_BINDGEN_USE_BROWSER=1 wasm-pack test --headless --chrome --firefox -- --features no-bundler
|
||||
|
||||
# Test from worker thread
|
||||
WASM_BINDGEN_USE_DEDICATED_WORKER=1 wasm-pack test --headless --chrome --firefox
|
||||
WASM_BINDGEN_USE_DEDICATED_WORKER=1 wasm-pack test --headless --chrome --firefox -- --features no-bundler
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/// Extracts current script file path from artificially generated stack trace
|
||||
function script_path() {
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
let parts = e.stack.match(/(?:\(|@)(\S+):\d+:\d+/);
|
||||
return parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
script_path()
|
||||
@@ -1,23 +0,0 @@
|
||||
import init, { web_spawn_recover_spawner } from "WASM_BINDGEN_SHIM_URL";
|
||||
|
||||
console.log('spawner spawned');
|
||||
|
||||
self.onmessage = event => {
|
||||
const [module_or_path, memory, spawner] = event.data;
|
||||
init({ module_or_path, memory })
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
// Propagate to main `onerror`:
|
||||
setTimeout(() => {
|
||||
throw err;
|
||||
});
|
||||
throw err;
|
||||
})
|
||||
.then(async () => {
|
||||
self.postMessage('ready');
|
||||
|
||||
await web_spawn_recover_spawner(spawner).run();
|
||||
|
||||
close();
|
||||
});
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
import init, { web_spawn_start_worker } from "WASM_BINDGEN_SHIM_URL";
|
||||
|
||||
self.onmessage = event => {
|
||||
const [module_or_path, memory, worker] = event.data;
|
||||
init({ module_or_path, memory })
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
// Propagate to main `onerror`:
|
||||
setTimeout(() => {
|
||||
throw err;
|
||||
});
|
||||
throw err;
|
||||
})
|
||||
.then(() => {
|
||||
self.postMessage('ready');
|
||||
|
||||
web_spawn_start_worker(worker);
|
||||
|
||||
close();
|
||||
});
|
||||
};
|
||||
@@ -21,16 +21,24 @@ pub(crate) type Closure = dyn FnOnce() + Send;
|
||||
/// Global sender channel for spawning threads.
|
||||
pub(crate) static SENDER: OnceLock<UnboundedSender<(Builder, Box<Closure>)>> = OnceLock::new();
|
||||
|
||||
/// Initializes the thread spawner.
|
||||
#[wasm_bindgen(js_name = initSpawner)]
|
||||
pub fn init_spawner() -> Spawner {
|
||||
Spawner::new()
|
||||
#[cfg_attr(not(feature = "no-bundler"), wasm_bindgen(module = "/js/spawn.js"))]
|
||||
#[cfg_attr(
|
||||
feature = "no-bundler",
|
||||
wasm_bindgen(module = "/js/spawn.no-bundler.js")
|
||||
)]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = startSpawnerWorker)]
|
||||
fn start_spawner_worker(module: JsValue, memory: JsValue, spawner: Spawner) -> Promise;
|
||||
}
|
||||
|
||||
/// Starts the thread spawner on a dedicated worker thread.
|
||||
#[wasm_bindgen(js_name = startSpawner)]
|
||||
pub fn start_spawner() -> Promise {
|
||||
Spawner::new().spawn()
|
||||
start_spawner_worker(
|
||||
wasm_bindgen::module(),
|
||||
wasm_bindgen::memory(),
|
||||
Spawner::new(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Spawns a closure onto a new thread.
|
||||
|
||||
@@ -2,21 +2,13 @@ use futures::{
|
||||
StreamExt,
|
||||
channel::mpsc::{UnboundedReceiver, unbounded},
|
||||
};
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::wasm::{
|
||||
Closure, SENDER,
|
||||
thread::Builder,
|
||||
utils::{callback, encode_script, get_shim_url},
|
||||
worker::WorkerData,
|
||||
};
|
||||
use crate::wasm::{Closure, SENDER, thread::Builder, worker::WorkerData};
|
||||
|
||||
/// Global spawner which spawns closures into web workers.
|
||||
#[wasm_bindgen]
|
||||
pub struct Spawner {
|
||||
shim_url: String,
|
||||
worker_url: String,
|
||||
receiver: UnboundedReceiver<(Builder, Box<Closure>)>,
|
||||
}
|
||||
|
||||
@@ -34,40 +26,26 @@ impl Spawner {
|
||||
panic!("spawner already initialized");
|
||||
}
|
||||
|
||||
let shim_url = get_shim_url();
|
||||
let worker_url = encode_script(&shim_url, include_str!("../js/worker.js"));
|
||||
|
||||
Self {
|
||||
shim_url,
|
||||
worker_url,
|
||||
receiver,
|
||||
}
|
||||
Self { receiver }
|
||||
}
|
||||
|
||||
/// Spawns the spawner into a dedicated web worker.
|
||||
pub fn spawn(self) -> Promise {
|
||||
let options = web_sys::WorkerOptions::new();
|
||||
options.set_type(web_sys::WorkerType::Module);
|
||||
options.set_name("web_spawn_spawner");
|
||||
#[cfg(feature = "no-bundler")]
|
||||
#[wasm_bindgen(js_name = getUrl)]
|
||||
pub fn get_url(&self) -> js_sys::JsString {
|
||||
crate::utils::get_url()
|
||||
}
|
||||
|
||||
let script_url = encode_script(&self.shim_url, include_str!("../js/spawner.js"));
|
||||
let worker = web_sys::Worker::new_with_options(&script_url, &options).unwrap_throw();
|
||||
|
||||
let data = js_sys::Array::new();
|
||||
data.push(&wasm_bindgen::module());
|
||||
data.push(&wasm_bindgen::memory());
|
||||
data.push(&JsValue::from(Box::into_raw(Box::new(self))));
|
||||
|
||||
worker.post_message(&data).unwrap_throw();
|
||||
|
||||
callback(&worker)
|
||||
#[wasm_bindgen(js_name = intoRaw)]
|
||||
pub fn into_raw(self) -> *mut Self {
|
||||
Box::into_raw(Box::new(self))
|
||||
}
|
||||
|
||||
/// Runs the spawner.
|
||||
pub async fn run(mut self) {
|
||||
#[wasm_bindgen]
|
||||
pub async fn run(&mut self, url: String) {
|
||||
// Spawn a new worker for every closure.
|
||||
while let Some((builder, f)) = self.receiver.next().await {
|
||||
WorkerData::new(f).spawn(builder, &self.worker_url);
|
||||
WorkerData::new(f).spawn(builder, &url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +1,12 @@
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{Blob, MessageEvent, Url, Worker};
|
||||
#[cfg(feature = "no-bundler")]
|
||||
pub(crate) fn get_url() -> js_sys::JsString {
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Returns the URL for the wasm bindgen shim.
|
||||
pub(crate) fn get_shim_url() -> String {
|
||||
js_sys::eval(include_str!("../js/script_path.js"))
|
||||
.unwrap_throw()
|
||||
.as_string()
|
||||
.unwrap_throw()
|
||||
}
|
||||
|
||||
/// Generates worker script as URL encoded blob
|
||||
pub(crate) fn encode_script(wasm_bindgen_shim_url: &str, template: &str) -> String {
|
||||
let script = template.replace("WASM_BINDGEN_SHIM_URL", &wasm_bindgen_shim_url);
|
||||
|
||||
// Create url encoded blob
|
||||
let arr = js_sys::Array::new();
|
||||
arr.set(0, JsValue::from_str(&script));
|
||||
let blob = Blob::new_with_str_sequence(&arr).unwrap();
|
||||
let url = Url::create_object_url_with_blob(
|
||||
&blob
|
||||
.slice_with_f64_and_f64_and_content_type(0.0, blob.size(), "text/javascript")
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
url
|
||||
}
|
||||
|
||||
pub(crate) fn callback(worker: &Worker) -> Promise {
|
||||
Promise::new(&mut |resolve, _reject| {
|
||||
// Create a one-time closure that resolves the promise when a message is
|
||||
// received.
|
||||
let callback = Closure::once(move |event: MessageEvent| {
|
||||
// Resolve the promise with the event's data.
|
||||
resolve.call1(&JsValue::NULL, &event.data()).unwrap();
|
||||
});
|
||||
|
||||
// Attach the callback to the worker's onmessage event.
|
||||
worker.set_onmessage(Some(callback.as_ref().unchecked_ref()));
|
||||
|
||||
// Ensure the callback isn't dropped prematurely.
|
||||
callback.forget();
|
||||
})
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(thread_local_v2, js_namespace = ["import", "meta"], js_name = url)]
|
||||
static URL: js_sys::JsString;
|
||||
}
|
||||
|
||||
URL.with(Clone::clone)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ impl WorkerData {
|
||||
|
||||
if let Some(name) = builder.name {
|
||||
options.set_name(&name);
|
||||
} else {
|
||||
options.set_name("web-spawn-worker");
|
||||
}
|
||||
|
||||
let worker = web_sys::Worker::new_with_options(script_url, &options).unwrap_throw();
|
||||
@@ -26,9 +28,15 @@ impl WorkerData {
|
||||
let data = js_sys::Array::new();
|
||||
data.push(&wasm_bindgen::module());
|
||||
data.push(&wasm_bindgen::memory());
|
||||
#[cfg(feature = "no-bundler")]
|
||||
data.push(&crate::utils::get_url());
|
||||
data.push(&JsValue::from(Box::into_raw(Box::new(self))));
|
||||
|
||||
worker.post_message(&data).unwrap_throw();
|
||||
let msg = js_sys::Object::new();
|
||||
js_sys::Reflect::set(&msg, &"type".into(), &"web_spawn_start_worker".into()).unwrap_throw();
|
||||
js_sys::Reflect::set(&msg, &"data".into(), &data).unwrap_throw();
|
||||
|
||||
worker.post_message(&msg).unwrap_throw();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user