mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: Worker 构造器解析 options.name 加 self.name 与 self.location
This commit is contained in:
Generated
+19
-19
@@ -1160,7 +1160,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_builtins"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fast-float",
|
||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_bytecode"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_log",
|
||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cfg"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_emit",
|
||||
@@ -1204,7 +1204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cli"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
@@ -1227,7 +1227,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_code_cache"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"oxide_bytecode",
|
||||
@@ -1236,7 +1236,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_compiler"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1256,7 +1256,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_dce"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1270,7 +1270,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
@@ -1285,7 +1285,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_engine"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1297,7 +1297,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_log",
|
||||
@@ -1306,7 +1306,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_kernel"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1320,7 +1320,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_liveness"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1332,7 +1332,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_log"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@@ -1341,7 +1341,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_parser"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -1356,7 +1356,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_regalloc"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1372,7 +1372,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_runtime_api"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -1385,7 +1385,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_test262"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1403,7 +1403,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_types"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1416,7 +1416,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_vm"
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.14.16"
|
||||
version = "0.14.17"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::bind_constructor;
|
||||
use crate::bindings::{apply_binding_table, bind_accessor_getset, configure_native_constructor};
|
||||
use crate::bindings::{apply_binding_table, bind_accessor_getset, bind_accessor_getter, configure_native_constructor};
|
||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID};
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::string_forge::PermInterner;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// 把 Worker 构造器(Box 分配)与 self 面方法绑定到 global。
|
||||
@@ -222,19 +223,42 @@ pub fn bind_worker(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
);
|
||||
}
|
||||
}
|
||||
// name / location getter(幂等守卫)。
|
||||
// let si_name_global = sf.intern("name").0;
|
||||
// if sh.lookup_position(global.shape_id(), si_name_global).is_none() {
|
||||
// bind_accessor_getter(core, session, global, "name", crate::worker::bindings::self_name as *const ());
|
||||
// }
|
||||
// let si_location = sf.intern("location").0;
|
||||
// if sh.lookup_position(global.shape_id(), si_location).is_none() {
|
||||
// bind_accessor_getter(
|
||||
// core,
|
||||
// session,
|
||||
// global,
|
||||
// "location",
|
||||
// crate::worker::bindings::self_location as *const (),
|
||||
// );
|
||||
// }
|
||||
// name / location:worker realm 绑 getter(读 thread-local 真值,规范中
|
||||
// WorkerGlobalScope.name 为只读访问器);主 realm 绑空串数据属性(与浏览器
|
||||
// window.name 同一形状的无害占位,不挡全局 var name 赋值)。幂等守卫。
|
||||
let worker_realm = crate::worker::bindings::worker_thread();
|
||||
let si_name_global = sf.intern("name").0;
|
||||
if sh.lookup_position(global.shape_id(), si_name_global).is_none() {
|
||||
if worker_realm {
|
||||
bind_accessor_getter(core, session, global, "name", crate::worker::bindings::self_name as *const ());
|
||||
} else {
|
||||
bind_global_empty_string_property(global, sf, sh, si_name_global);
|
||||
}
|
||||
}
|
||||
let si_location = sf.intern("location").0;
|
||||
if sh.lookup_position(global.shape_id(), si_location).is_none() {
|
||||
if worker_realm {
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
global,
|
||||
"location",
|
||||
crate::worker::bindings::self_location as *const (),
|
||||
);
|
||||
} else {
|
||||
bind_global_empty_string_property(global, sf, sh, si_location);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 在 global 上绑一个空串数据属性(主 realm 的 name / location 占位)。
|
||||
fn bind_global_empty_string_property(global: &mut JsObject, sf: &PermInterner, sh: &ShapeForge, si: u32) {
|
||||
let shape = sh.make_shape(global.shape_id(), si);
|
||||
global.set_shape_id(shape);
|
||||
global
|
||||
.ensure_hash_props()
|
||||
.push(JsValue::perm_string(sf.string_ptr(sf.intern("").0)));
|
||||
let pos = global.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
|
||||
global.set_data_meta(pos, PropAttributes::new(true, false, true));
|
||||
global.bump_generation();
|
||||
}
|
||||
|
||||
@@ -36,6 +36,16 @@ thread_local! {
|
||||
static WORKER_CLOSE_REQUESTED: Cell<bool> = const { Cell::new(false) };
|
||||
}
|
||||
|
||||
// worker 名称(构造器 options.name,worker 线程独占,主 realm 不置位)。
|
||||
thread_local! {
|
||||
static WORKER_NAME: RefCell<Option<String>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
// worker 位置(构造器 url 字符串,worker 线程独占,主 realm 不置位)。
|
||||
thread_local! {
|
||||
static WORKER_LOCATION: RefCell<Option<String>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
/// 注入 worker → 主线程输出通道(`worker_event_loop` 建 Vm 后调用)。
|
||||
///
|
||||
/// # 副作用
|
||||
@@ -57,17 +67,34 @@ pub(crate) fn worker_close_requested() -> bool {
|
||||
WORKER_CLOSE_REQUESTED.with(|flag| flag.get())
|
||||
}
|
||||
|
||||
/// `new Worker(url)`:读 url 文件、派生 worker、建 Worker 对象。
|
||||
/// 注入 worker 名称与位置(`worker_event_loop` 建 Vm 前调用)。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 覆盖本线程的 `WORKER_NAME` 与 `WORKER_LOCATION`(worker 线程内恰好一次)。
|
||||
/// - 建 Vm 前调用:`bind_worker` 据本线程是否置位区分 worker realm 与主 realm。
|
||||
pub(crate) fn set_worker_identity(name: String, location: String) {
|
||||
WORKER_NAME.with(|slot| *slot.borrow_mut() = Some(name));
|
||||
WORKER_LOCATION.with(|slot| *slot.borrow_mut() = Some(location));
|
||||
}
|
||||
|
||||
/// 查询当前线程是否 worker 线程(绑定层区分 worker realm 与主 realm)。
|
||||
pub(crate) fn worker_thread() -> bool {
|
||||
WORKER_NAME.with(|slot| slot.borrow().is_some())
|
||||
}
|
||||
|
||||
/// `new Worker(url, options)`:读 url 文件与 options.name、派生 worker、建 Worker 对象。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 构造形态校验(HTML 规范只有 `[[Construct]]`,非构造调用抛 TypeError)。
|
||||
/// 2. 读 `args[1]` 为 url 字符串(非字符串抛 TypeError)。
|
||||
/// 3. 主线程侧读 url 文件(`std::fs::read_to_string`),失败抛 TypeError。
|
||||
/// 4. `vm.spawn_worker(source)` 派生 worker,得 worker 编号。
|
||||
/// 5. 建 Worker 对象:PLAIN 对象(`[[Prototype]]` → Worker.prototype)加非枚举
|
||||
/// 3. 读 `args[2]` 为 options 对象(缺省 undefined);是对象时经 `ordinary_get`
|
||||
/// 读 `name` 属性(getter 副作用与异常原值传播),是字符串取之、非字符串取空串。
|
||||
/// 4. 主线程侧读 url 文件(`std::fs::read_to_string`),失败抛 TypeError。
|
||||
/// 5. `vm.spawn_worker(source, name, url)` 派生 worker,得 worker 编号。
|
||||
/// 6. 建 Worker 对象:PLAIN 对象(`[[Prototype]]` → Worker.prototype)加非枚举
|
||||
/// `workerId` 数据属性(值为 worker 编号)。`onmessage`/`onerror`/
|
||||
/// `onmessageerror` 不在对象上建自有属性(改原型访问器属性)。无 native 盒、无 GC 家族。
|
||||
/// 6. 返回 Worker 对象。
|
||||
/// 7. 返回 Worker 对象。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `url` 是 worker 脚本的文件路径(主线程侧读取)。
|
||||
@@ -89,6 +116,24 @@ pub(crate) fn worker_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
None => return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "Worker url must be a string")),
|
||||
};
|
||||
|
||||
// 读 options 对象(args[2],缺省 undefined)。
|
||||
let options_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
// name:options 是对象时经 ordinary_get 读 name 属性(getter 副作用与异常
|
||||
// 原值传播),是字符串取之,非字符串或缺省取空串。
|
||||
let name = if options_val.is_object() {
|
||||
let opts_ptr = options_val.as_js_object_ptr();
|
||||
// SAFETY: opts_ptr 是 options 对象,native 调用期间存活。
|
||||
let opts_obj = unsafe { &*opts_ptr };
|
||||
let si_name = vm.perm_intern("name");
|
||||
let name_val = match vm.ordinary_get(opts_obj, si_name, options_val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(oxide_builtins::error::create_type_error(vm, &e)),
|
||||
};
|
||||
vm.lookup_str(name_val).unwrap_or_default()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
// 主线程侧读 url 文件(worker 脚本源码)。
|
||||
let source = match std::fs::read_to_string(&url) {
|
||||
Ok(s) => s,
|
||||
@@ -100,8 +145,8 @@ pub(crate) fn worker_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
};
|
||||
|
||||
// 派生 worker(建 WorkerHandle 并 spawn OS 线程)。
|
||||
let id = match vm.spawn_worker(&source) {
|
||||
// 派生 worker(建 WorkerHandle 并 spawn OS 线程),传 name 与 url(location)。
|
||||
let id = match vm.spawn_worker(&source, &name, &url) {
|
||||
Ok(id) => id,
|
||||
Err(e) => return NativeResult::Err(oxide_builtins::error::create_type_error(vm, &e)),
|
||||
};
|
||||
@@ -275,16 +320,16 @@ pub(crate) fn self_close(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `self.name` getter(worker 侧):返空串(首版占位,规范面对齐留后续)。
|
||||
#[expect(dead_code)]
|
||||
/// `self.name` getter(worker 侧):读本线程 worker 名称(未置位时返空串)。
|
||||
pub(crate) fn self_name(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(vm.new_string(""))
|
||||
let name = WORKER_NAME.with(|slot| slot.borrow().as_deref().unwrap_or("").to_string());
|
||||
NativeResult::Ok(vm.new_string(&name))
|
||||
}
|
||||
|
||||
/// `self.location` getter(worker 侧):返空串(首版占位,规范面对齐留后续)。
|
||||
#[expect(dead_code)]
|
||||
/// `self.location` getter(worker 侧):读本线程 worker 位置(未置位时返空串)。
|
||||
pub(crate) fn self_location(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(vm.new_string(""))
|
||||
let location = WORKER_LOCATION.with(|slot| slot.borrow().as_deref().unwrap_or("").to_string());
|
||||
NativeResult::Ok(vm.new_string(&location))
|
||||
}
|
||||
|
||||
/// 从当前函数对象的 `name` 读事件类型(`get onmessage` / `set onmessage` →
|
||||
|
||||
@@ -81,23 +81,31 @@ pub struct WorkerHandle {
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 经 `Vm::with_kernel_core` 建专属 Vm(新 realm)并注入编译服务。
|
||||
/// 2. 编译 worker 脚本(worker 的程序);失败则经 worker → 主线程通道错误面上报
|
||||
/// 2. 注入 worker → 主线程输出通道与名称、位置(thread-local,模式同 `WORKER_OUT_TX`)。
|
||||
/// 3. 编译 worker 脚本(worker 的程序);失败则经 worker → 主线程通道错误面上报
|
||||
/// 错误串,不 panic,worker 继续事件循环(可被干净终止)。
|
||||
/// 3. 编译成功则运行脚本(worker 的程序);运行期未捕获异常同样经错误面上报。
|
||||
/// 4. 循环 `recv_timeout(100ms)`:处理 `Message` / `Error` / `Terminate` / 超时。
|
||||
/// 4. 编译成功则运行脚本(worker 的程序);运行期未捕获异常同样经错误面上报。
|
||||
/// 5. 循环 `recv_timeout(100ms)`:处理 `Message` / `Error` / `Terminate` / 超时。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `script` 是 worker 的程序源码(普通脚本),编译与运行均在 worker 线程内完成。
|
||||
/// - `name` / `location` 是 worker 的名称与位置(构造器 options.name 与 url),
|
||||
/// 置入 thread-local 供 `self.name` / `self.location` getter 读取。
|
||||
/// - 超时后查 `rx.is_disconnected()`:主线程全部发送端 drop 即断开,worker 退出。
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 创建并 drop 一个 Vm(退出时 drop 触发 `Drop for Vm` → `Drop for Realm`)。
|
||||
/// - 置位本线程的 `WORKER_NAME` / `WORKER_LOCATION` thread-local(模式同
|
||||
/// `WORKER_OUT_TX`,线程退出随 thread-local 释放)。
|
||||
/// - 每条 `Message` 经 rehydrate → onmessage 交付 → drain_microtasks 处理;
|
||||
/// 编译或执行失败经 `WorkerOutMail::Error` 上报主线程(主线程交付 onerror)。
|
||||
fn worker_event_loop(
|
||||
core: Arc<KernelCore>, compiler: Arc<dyn CompilerService>, script: String, rx: Receiver<WorkerMail>,
|
||||
out_tx: Sender<WorkerOutMail>,
|
||||
out_tx: Sender<WorkerOutMail>, name: String, location: String,
|
||||
) {
|
||||
// 注入 worker 名称与位置(建 Vm 前:bind_worker 据本线程 thread-local 区分
|
||||
// worker realm 与主 realm;self.name / self.location 经 thread-local 读取)。
|
||||
bindings::set_worker_identity(name, location);
|
||||
let mut vm = Vm::with_kernel_core(core);
|
||||
vm.set_compiler_service(Arc::clone(&compiler));
|
||||
// 注入 worker → 主线程输出通道(self.postMessage 经 thread-local 发回主线程)。
|
||||
@@ -157,12 +165,15 @@ impl Vm {
|
||||
/// # 步骤
|
||||
/// 1. 分配 worker 编号(`worker_next_id` 自增)。
|
||||
/// 2. 建主线程 → worker 与 worker → 主线程两条通道。
|
||||
/// 3. spawn 线程,线程内建专属 Vm 并进入事件循环。
|
||||
/// 3. spawn 线程,线程内建专属 Vm 并进入事件循环(`name` / `location`
|
||||
/// 物化为 `String` 随线程闭包传入,置入 worker 线程 thread-local)。
|
||||
/// 4. 把 `WorkerHandle` 登记进 `worker_registry`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `script` 是 worker 的程序源码(普通脚本);编译在 worker 线程内完成,
|
||||
/// 编译失败经 worker → 主线程通道上报,不 panic。
|
||||
/// - `name` 是 worker 名称(构造器 options.name,缺省空串);`location`
|
||||
/// 是 worker 位置(构造器 url 字符串),供 `self.name` / `self.location` 读取。
|
||||
/// - realm 计数受 512 上界约束(每 worker 一个 realm),worker 数须远低于 512。
|
||||
///
|
||||
/// # 返回值
|
||||
@@ -170,7 +181,7 @@ impl Vm {
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 派生一个 OS 线程;登记一个 `WorkerHandle`。
|
||||
pub fn spawn_worker(&mut self, script: &str) -> Result<u64, String> {
|
||||
pub fn spawn_worker(&mut self, script: &str, name: &str, location: &str) -> Result<u64, String> {
|
||||
let id = self.worker_next_id;
|
||||
self.worker_next_id += 1;
|
||||
|
||||
@@ -180,9 +191,11 @@ impl Vm {
|
||||
let core = Arc::clone(&self.kernel_core);
|
||||
let compiler = Arc::clone(&self.compiler);
|
||||
let script_owned = script.to_string();
|
||||
let name_owned = name.to_string();
|
||||
let location_owned = location.to_string();
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
worker_event_loop(core, compiler, script_owned, rx, out_tx);
|
||||
worker_event_loop(core, compiler, script_owned, rx, out_tx, name_owned, location_owned);
|
||||
});
|
||||
|
||||
let worker = WorkerHandle { id, tx, rx_out: out_rx, handle };
|
||||
@@ -550,7 +563,7 @@ mod tests {
|
||||
|
||||
// worker 脚本设 addEventListener 回显(注册表交付替换早期 onmessage 属性语义)。
|
||||
let id = vm
|
||||
.spawn_worker("self.addEventListener('message', function(e) { self.postMessage(e.data); });")
|
||||
.spawn_worker("self.addEventListener('message', function(e) { self.postMessage(e.data); });", "", "")
|
||||
.expect("worker 应派生成功");
|
||||
assert_eq!(vm.active_workers(), vec![id], "应有唯一活跃 worker");
|
||||
|
||||
@@ -569,7 +582,7 @@ mod tests {
|
||||
fn worker_script_compile_failure_reports_error() {
|
||||
let mut vm = vm_with_compiler();
|
||||
|
||||
let id = vm.spawn_worker("function { 语法错误").expect("worker 应派生成功");
|
||||
let id = vm.spawn_worker("function { 语法错误", "", "").expect("worker 应派生成功");
|
||||
let poll = poll_until_mail(&mut vm, id);
|
||||
assert!(!poll.errors.is_empty(), "编译失败应经错误面上报错误串");
|
||||
assert!(!poll.errors[0].is_empty(), "错误串应非空");
|
||||
@@ -586,10 +599,10 @@ mod tests {
|
||||
|
||||
// 两 worker 各设 addEventListener 回显(注册表交付语义)。
|
||||
let id_a = vm
|
||||
.spawn_worker("self.addEventListener('message', function(e) { self.postMessage(e.data); });")
|
||||
.spawn_worker("self.addEventListener('message', function(e) { self.postMessage(e.data); });", "", "")
|
||||
.expect("worker A 应派生成功");
|
||||
let id_b = vm
|
||||
.spawn_worker("self.addEventListener('message', function(e) { self.postMessage(e.data); });")
|
||||
.spawn_worker("self.addEventListener('message', function(e) { self.postMessage(e.data); });", "", "")
|
||||
.expect("worker B 应派生成功");
|
||||
assert_eq!(vm.active_workers(), vec![id_a, id_b], "应有两个活跃 worker");
|
||||
|
||||
@@ -689,7 +702,7 @@ mod tests {
|
||||
fn worker_e2e_terminate() {
|
||||
let mut vm = vm_with_compiler();
|
||||
|
||||
let id = vm.spawn_worker("1").expect("worker 应派生成功");
|
||||
let id = vm.spawn_worker("1", "", "").expect("worker 应派生成功");
|
||||
assert_eq!(vm.active_workers(), vec![id], "应有唯一活跃 worker");
|
||||
|
||||
vm.worker_terminate(id).expect("终止应成功");
|
||||
@@ -707,7 +720,7 @@ mod tests {
|
||||
let core = Arc::clone(vm.kernel_core());
|
||||
let baseline = core.active_vms();
|
||||
|
||||
let id = vm.spawn_worker("1").expect("worker 应派生成功");
|
||||
let id = vm.spawn_worker("1", "", "").expect("worker 应派生成功");
|
||||
// worker 线程异步建 Vm:轮询至 realm 计数 +1(带截止,防 flaky)。
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
while core.active_vms() < baseline + 1 {
|
||||
@@ -865,6 +878,65 @@ mod tests {
|
||||
let _ = std::fs::remove_file(&worker_path);
|
||||
}
|
||||
|
||||
/// 端到端:带 name 的 Worker 内 self.name 为 name、self.location 为 url 字符串;
|
||||
/// 无 options 的 Worker 内 self.name 为空串。
|
||||
#[test]
|
||||
fn worker_e2e_name_and_location() {
|
||||
let mut vm = vm_with_compiler();
|
||||
|
||||
// worker 脚本:收到消息后回发 self.name 与 self.location(以 | 连接)。
|
||||
let worker_script =
|
||||
"self.addEventListener('message', function(e) { self.postMessage(self.name + '|' + self.location); });";
|
||||
let path_a = write_worker_script(worker_script);
|
||||
let path_b = write_worker_script(worker_script);
|
||||
// 主脚本:A 带 options.name 建 Worker、B 无 options,各自回发后记录。
|
||||
let main_script = format!(
|
||||
"var wa = new Worker('{a}', {{ name: 'n' }}); var wb = new Worker('{b}'); \
|
||||
wa.addEventListener('message', function(e) {{ globalThis.idA = e.data; }}); \
|
||||
wb.addEventListener('message', function(e) {{ globalThis.idB = e.data; }}); \
|
||||
wa.postMessage('check'); wb.postMessage('check');",
|
||||
a = path_a.display(),
|
||||
b = path_b.display()
|
||||
);
|
||||
vm.run(&Arc::new(compile_script(&main_script))).expect("主脚本应运行");
|
||||
|
||||
// 事件循环轮询至两个回发都到达(带截止,防 flaky)。
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
loop {
|
||||
vm.deliver_worker_messages();
|
||||
vm.cleanup_disconnected_workers();
|
||||
let a = global_value_opt(&vm, "idA");
|
||||
let b = global_value_opt(&vm, "idB");
|
||||
if a.is_some_and(|v| v.is_string()) && b.is_some_and(|v| v.is_string()) {
|
||||
break;
|
||||
}
|
||||
if Instant::now() >= deadline {
|
||||
panic!("5 秒内未收齐 self.name 与 self.location 回发");
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
// A:self.name 为 "n"、self.location 为 url 字符串。
|
||||
let id_a = global_value_opt(&vm, "idA").expect("idA 应被设置");
|
||||
let id_a_text = vm.lookup_str(id_a).expect("idA 应可读");
|
||||
assert_eq!(
|
||||
id_a_text,
|
||||
format!("n|{}", path_a.display()),
|
||||
"带 name 的 worker 内 self.name 应为 \"n\"、self.location 应为 url 字符串"
|
||||
);
|
||||
// B:无 options 时 self.name 为空串、self.location 仍为 url 字符串。
|
||||
let id_b = global_value_opt(&vm, "idB").expect("idB 应被设置");
|
||||
let id_b_text = vm.lookup_str(id_b).expect("idB 应可读");
|
||||
assert_eq!(
|
||||
id_b_text,
|
||||
format!("|{}", path_b.display()),
|
||||
"无 options 的 worker 内 self.name 应为空串、self.location 应为 url 字符串"
|
||||
);
|
||||
|
||||
vm.shutdown_workers();
|
||||
let _ = std::fs::remove_file(&path_a);
|
||||
let _ = std::fs::remove_file(&path_b);
|
||||
}
|
||||
|
||||
/// onmessageerror 交付辅助:无自然触发,直接单测交付辅助(handler 被调用、
|
||||
/// 事件 type 为 "messageerror")。不钉触发可达性(当前架构不可达)。
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user