From 6f1df99a4ff52fc6a4fa07d0956208657ad5d5e0 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 9 Oct 2026 06:50:04 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Worker=20=E6=9E=84=E9=80=A0=E5=99=A8?= =?UTF-8?q?=E4=B8=8E=20postMessage=20=E4=B8=8E=20terminate=20=E4=B8=8E=20s?= =?UTF-8?q?elf=20=E9=9D=A2=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 +-- Cargo.toml | 2 +- crates/oxide_vm/src/bindings/bind_worker.rs | 174 +++++++++++ crates/oxide_vm/src/bindings/mod.rs | 6 + crates/oxide_vm/src/worker/bindings.rs | 313 ++++++++++++++++++++ crates/oxide_vm/src/worker/mod.rs | 8 + 6 files changed, 521 insertions(+), 20 deletions(-) create mode 100644 crates/oxide_vm/src/bindings/bind_worker.rs create mode 100644 crates/oxide_vm/src/worker/bindings.rs diff --git a/Cargo.lock b/Cargo.lock index 2dc7652b..24070ed9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.91" +version = "0.8.92" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.91" +version = "0.8.92" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.91" +version = "0.8.92" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.91" +version = "0.8.92" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.91" +version = "0.8.92" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.91" +version = "0.8.92" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.91" +version = "0.8.92" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.91" +version = "0.8.92" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.8.91" +version = "0.8.92" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.91" +version = "0.8.92" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.91" +version = "0.8.92" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.91" +version = "0.8.92" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.91" +version = "0.8.92" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.91" +version = "0.8.92" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.91" +version = "0.8.92" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.91" +version = "0.8.92" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.91" +version = "0.8.92" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.91" +version = "0.8.92" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.91" +version = "0.8.92" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 7d0022ef..8217a407 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.8.91" +version = "0.8.92" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_vm/src/bindings/bind_worker.rs b/crates/oxide_vm/src/bindings/bind_worker.rs new file mode 100644 index 00000000..107144c9 --- /dev/null +++ b/crates/oxide_vm/src/bindings/bind_worker.rs @@ -0,0 +1,174 @@ +//! Worker 构造器绑定:Box 分配构造器(不占 `BuiltinWorld` P 字段、不新增 +//! `BuiltinId`、不占 `BuiltinDirtySet` 脏位)加 `track_leaked_object` 加填原型 +//! 占位加全局槽,全槽 `lookup_position` 幂等守卫。 +//! +//! 与 `bind_iterator_global` 同型:构造器经 `Box::into_raw` 登记 world 释放 +//! 登记表(session 收尾统一释放),全局 `Worker` 槽经 `bind_constructor!` 原位 +//! 安装(既有槽更新槽值、无槽新开)。 + +use std::sync::Arc; + +use crate::bind_constructor; +use crate::bindings::{apply_binding_table, configure_native_constructor}; +use oxide_kernel::kernel::{KernelCore, KernelSession}; +use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_types::object::{JsObject, PropAttributes}; +use oxide_types::value::JsValue; + +/// 把 Worker 构造器(Box 分配)与 self 面方法绑定到 global。 +/// +/// # 幂等 +/// 本函数从三条路径到达:`init_kernel_builtins`(初始)、`rebind_dirty_builtins` +/// 的 `worker` 分支(无条件,构造器不在脏体系内)、global-dirty 路径 +/// (`bind_global_builtin_slots`)。构造器本体经 `lookup_position` 守卫复用(有 +/// 槽即不新建);原型槽位经 `lookup_position` 守卫(有槽即跳过); +/// `bind_constructor!` 的全局槽臂自带既有槽原位更新。 +/// +/// # 副作用 +/// - 构造器与原型对象经 `Box::into_raw` 登记 world 释放登记表(session 收尾统一 +/// 释放);全局 `Worker` 槽经 `bind_constructor!` 原位安装。 +/// - self 面方法(`postMessage`/`close` 方法、`name`/`location` getter)绑 global +/// (worker realm 经 `self` === global 可达,主 realm 无 `self` 时为无害占位)。 +/// +/// `realm_id` 未使用(Worker 无 well-known 符号键),保留签名与其他绑定函数一致。 +pub fn bind_worker(core: &Arc, session: &KernelSession, global: &mut JsObject, _realm_id: u32) { + let world = session.builtin_world(); + let sf = core.perm_interner().as_ref(); + let sh = core.shape_forge().as_ref(); + + // ---- 构造器本体(Box 分配,不占 P 字段)---- + // 幂等:全局 `Worker` 槽已有构造器时复用(不新建)。 + let si_worker = sf.intern("Worker").0; + let ctor_ptr: *mut JsObject = match sh.lookup_position(global.shape_id(), si_worker) { + Some(pos) if global.get_prop_at(pos).is_object() => global.get_prop_at(pos).as_js_object_ptr(), + _ => { + let function_proto = world.function_proto.as_ptr() as *mut JsObject; + let mut ctor = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto)); + ctor.set_function(true); + configure_native_constructor( + &mut ctor, + crate::worker::bindings::worker_constructor as *const (), + 1, + ); + let ptr = Box::into_raw(Box::new(ctor)); + world.track_leaked_object(ptr); + ptr + } + }; + + // 构造器 name 槽(描述符 { f,f,t }),幂等守卫。 + let si_name = sf.intern("name").0; + { + let ctor = unsafe { &mut *ctor_ptr }; + if sh.lookup_position(ctor.shape_id(), si_name).is_none() { + let shape = sh.make_shape(ctor.shape_id(), si_name); + ctor.set_shape_id(shape); + ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(sf.intern("Worker").0))); + let pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(pos, PropAttributes::new(false, false, true)); + ctor.bump_generation(); + } + } + + // ---- 原型占位 ---- + // 幂等:构造器 prototype 槽已有原型时复用(不新建)。 + let si_prototype = sf.intern("prototype").0; + let proto_ptr: *mut JsObject = { + let ctor = unsafe { &*ctor_ptr }; + match sh.lookup_position(ctor.shape_id(), si_prototype) { + Some(pos) if ctor.get_prop_at(pos).is_object() => ctor.get_prop_at(pos).as_js_object_ptr(), + _ => { + let object_proto = world.object_proto.as_ptr() as *mut JsObject; + let proto = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)); + let ptr = Box::into_raw(Box::new(proto)); + world.track_leaked_object(ptr); + // 回填构造器 prototype 槽(描述符 { f,f,f })。 + let ctor = unsafe { &mut *ctor_ptr }; + let shape = sh.make_shape(ctor.shape_id(), si_prototype); + ctor.set_shape_id(shape); + ctor.ensure_hash_props().push(JsValue::from_js_object(ptr)); + let pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(pos, PropAttributes::new(false, false, false)); + ctor.bump_generation(); + ptr + } + } + }; + + { + let proto = unsafe { &mut *proto_ptr }; + + // constructor 槽 → 构造器对象(描述符 { t,f,t }),幂等守卫。 + let si_constructor = sf.intern("constructor").0; + if sh.lookup_position(proto.shape_id(), si_constructor).is_none() { + let shape = sh.make_shape(proto.shape_id(), si_constructor); + proto.set_shape_id(shape); + let pos = proto.push_prop(JsValue::from_js_object(ctor_ptr)); + proto.set_data_meta(pos, PropAttributes::new(true, false, true)); + proto.bump_generation(); + } + + // postMessage / terminate 原型方法(幂等守卫)。 + let si_post_message = sf.intern("postMessage").0; + if sh.lookup_position(proto.shape_id(), si_post_message).is_none() { + apply_binding_table( + world, + proto, + core, + &[ + ( + "postMessage", + crate::worker::bindings::worker_post_message as *const (), + 1, + ), + ("terminate", crate::worker::bindings::worker_terminate as *const (), 0), + ], + ); + } + } + + // 全局 `Worker` 槽(bind_constructor! 自带既有槽原位更新、无槽新开)。 + bind_constructor!( + core, + global, + "Worker", + ctor_ptr, + crate::worker::bindings::worker_constructor, + 1, + hash: true + ); + + // ---- self 面(绑 global,worker realm 经 self === global 可达)---- + // postMessage / close 方法(幂等守卫)。 + let si_self_post = sf.intern("postMessage").0; + if sh.lookup_position(global.shape_id(), si_self_post).is_none() { + apply_binding_table( + world, + global, + core, + &[ + ( + "postMessage", + crate::worker::bindings::self_post_message as *const (), + 1, + ), + ("close", crate::worker::bindings::self_close as *const (), 0), + ], + ); + } + // 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 (), + // ); + // } +} diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 89dc3096..2acd6239 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -56,6 +56,8 @@ pub mod bind_shared_array_buffer; pub mod bind_string; /// 未实现内置(Proxy/BigInt/WeakMap 等)的 stub 构造器绑定。 pub mod bind_stubs; +/// Worker 构造器绑定(Box 分配,不占 BuiltinWorld P 字段)。 +pub mod bind_worker; /// Symbol 构造器与原型的 native 方法绑定。 pub mod bind_symbol; /// Temporal 命名空间对象(Now/Instant/PlainDate/PlainTime)绑定。 @@ -1233,6 +1235,7 @@ pub fn bind_global_builtin_slots( bind_reflect_global(core, session, global); bind_iterator_global(core, session, global); + bind_worker::bind_worker(core, session, global, realm_id); bind_disposable_stack::bind_disposable_stack(core, session, global, realm_id); bind_async_disposable_stack::bind_async_disposable_stack(core, session, global, realm_id); bind_stub_globals(core, session, global); @@ -1343,6 +1346,9 @@ pub fn rebind_dirty_builtins( if dirty.map_or(true, |d| d.console) { bind_console::bind_console(core, session, global); } + // worker 构造器是 Box 分配对象(不在 BuiltinWorld 快照内),不占脏位: + // 无条件重绑(构造器经 lookup_position 守卫复用,幂等)。 + bind_worker::bind_worker(core, session, global, realm_id); // tag 目标横跨多个脏分组(Map/Set/DataView/Math/JSON/迭代器原型), // 各组各自重绑后统一补装;安装点自带幂等检查。 install_to_string_tags(core, session, realm_id); diff --git a/crates/oxide_vm/src/worker/bindings.rs b/crates/oxide_vm/src/worker/bindings.rs new file mode 100644 index 00000000..ba5a9de2 --- /dev/null +++ b/crates/oxide_vm/src/worker/bindings.rs @@ -0,0 +1,313 @@ +//! Worker API 的 concrete `&mut Vm` native 函数:构造器、`postMessage`/`terminate` +//! 原型方法、`self` 面(`self.postMessage`/`self.close`/`self.name`/`self.location`) +//! 与 `MessageEvent` 构造辅助。 +//! +//! 关键约定: +//! - 函数是 concrete `&mut Vm` 形态(`NativeFn`),不挂 `VmHost` trait(trait 是单 +//! realm 接口,worker 方法跨线程不挂 trait,见 934.9 边界条款)。 +//! - Worker 构造器走 Box 分配(不占 `BuiltinWorld` P 字段、不新增 `BuiltinId`、 +//! 不占 `BuiltinDirtySet` 脏位),由 `bind_worker` 安装。 +//! - `self_*` 方法绑 worker realm 的 global(`self` === global 故经 `self` 可达); +//! 主 realm 无 `self`,方法作为全局函数存在(无害占位)。 +//! - worker → 主线程输出通道与自关停请求位经 thread-local 传递(worker 线程独占, +//! 不跨线程共享),`worker_event_loop` 建 Vm 后注入。 + +use std::cell::{Cell, RefCell}; + +use oxide_builtins::message_value::{detach_message, MessageValue}; +use oxide_kernel::message_queue::Sender; +use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_runtime_api::{NativeResult, ProtoKind, VmHost}; +use oxide_types::object::{JsObject, PropAttributes}; +use oxide_types::value::JsValue; + +use crate::vm::Vm; + +// worker → 主线程输出通道(worker realm 专用,`worker_event_loop` 建 Vm 后注入)。 +// thread-local:worker 线程独占,不跨线程共享。主 realm 的 Vm 不注入(恒 `None`)。 +thread_local! { + static WORKER_OUT_TX: RefCell>> = const { RefCell::new(None) }; +} + +// worker 自关停请求位(`self.close()` 置位,事件循环每轮检查)。 +thread_local! { + static WORKER_CLOSE_REQUESTED: Cell = const { Cell::new(false) }; +} + +/// 注入 worker → 主线程输出通道(`worker_event_loop` 建 Vm 后调用)。 +/// +/// # 副作用 +/// - 覆盖本线程的 `WORKER_OUT_TX`(worker 线程内恰好一次)。 +pub(crate) fn set_worker_out_tx(tx: Sender) { + WORKER_OUT_TX.with(|slot| *slot.borrow_mut() = Some(tx)); +} + +/// 置位 worker 自关停请求(`self.close()` 调用)。 +/// +/// # 副作用 +/// - 置位本线程的 `WORKER_CLOSE_REQUESTED`(事件循环每轮检查后退出)。 +pub(crate) fn request_worker_close() { + WORKER_CLOSE_REQUESTED.with(|flag| flag.set(true)); +} + +/// 查询 worker 自关停请求位(事件循环每轮检查)。 +pub(crate) fn worker_close_requested() -> bool { + WORKER_CLOSE_REQUESTED.with(|flag| flag.get()) +} + +/// `new Worker(url)`:读 url 文件、派生 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)加非枚举 +/// `workerId` 数据属性(值为 worker 编号)与 `onmessage`/`onerror`/ +/// `onmessageerror` 普通 own 属性(初值 undefined)。无 native 盒、无 GC 家族。 +/// 6. 返回 Worker 对象。 +/// +/// # 边界与前提 +/// - `url` 是 worker 脚本的文件路径(主线程侧读取)。 +/// - Worker 对象经 `alloc_object` 入对象表即成 GC 根;`workerId` 是数值(无 GC 边)。 +/// +/// # 副作用 +/// - 派生一个 OS 线程(`spawn_worker`);登记一个 `WorkerHandle`。 +pub(crate) fn worker_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { + // 构造形态校验(HTML 规范只有 [[Construct]])。 + if !vm.constructing_native { + return NativeResult::Err(oxide_builtins::error::create_type_error(vm, "Worker constructor requires new")); + } + + // 读 url 字符串(args[1])。 + let url_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let url = match vm.lookup_str(url_val) { + Some(u) => u, + None => { + return NativeResult::Err(oxide_builtins::error::create_type_error( + vm, + "Worker url must be a string", + )) + } + }; + + // 主线程侧读 url 文件(worker 脚本源码)。 + let source = match std::fs::read_to_string(&url) { + Ok(s) => s, + Err(e) => { + return NativeResult::Err(oxide_builtins::error::create_type_error( + vm, + &format!("failed to read worker script: {e}"), + )) + } + }; + + // 派生 worker(建 WorkerHandle 并 spawn OS 线程)。 + let id = match vm.spawn_worker(&source) { + Ok(id) => id, + Err(e) => return NativeResult::Err(oxide_builtins::error::create_type_error(vm, &e)), + }; + + // 建 Worker 对象:PLAIN 对象([[Prototype]] → Worker.prototype)。 + let worker_proto_val = worker_prototype_value(vm); + let obj = JsObject::new_empty(EMPTY_SHAPE_ID, worker_proto_val); + let ptr = vm.alloc_object(obj); + // SAFETY: ptr 是本函数刚 alloc_object 的对象,存活且本段无别名。 + let worker_obj = unsafe { &mut *ptr }; + + // onmessage / onerror / onmessageerror:普通 own 数据属性(非枚举),初值 undefined。 + let si_onmessage = vm.perm_intern("onmessage"); + let _ = vm.define_data_property(worker_obj, si_onmessage, JsValue::undefined(), PropAttributes::new(true, false, true)); + let si_onerror = vm.perm_intern("onerror"); + let _ = vm.define_data_property(worker_obj, si_onerror, JsValue::undefined(), PropAttributes::new(true, false, true)); + let si_onmessageerror = vm.perm_intern("onmessageerror"); + let _ = vm.define_data_property( + worker_obj, + si_onmessageerror, + JsValue::undefined(), + PropAttributes::new(true, false, true), + ); + + // workerId:非枚举数据属性(值为 worker 编号,数值表示无 GC 边)。 + let si_worker_id = vm.perm_intern("workerId"); + let _ = vm.define_data_property(worker_obj, si_worker_id, JsValue::float(id as f64), PropAttributes::new(true, false, true)); + + NativeResult::Ok(JsValue::from_js_object(ptr)) +} + +/// 读全局 `Worker` 构造器的 `prototype` 属性(Worker.prototype)。 +/// +/// # 边界 +/// - 全局 `Worker` 槽缺失或构造器无 `prototype` 属性时回退到 `%Object.prototype%` +/// (防御性兜底,正常路径 `bind_worker` 已安装)。 +fn worker_prototype_value(vm: &mut Vm) -> JsValue { + let object_proto = JsValue::from_js_object(vm.builtin_proto(ProtoKind::ObjectProto)); + let global = vm.global_object(); + let global_ptr = global.as_ptr() as *mut JsObject; + // SAFETY: global_ptr 是当前 session 的 global 对象,存活。 + let global_obj = unsafe { &*global_ptr }; + let si_worker = vm.perm_intern("Worker"); + let Some(store) = vm.get_own_property_slot(global_obj, si_worker) else { + return object_proto; + }; + let ctor_val = global_obj.get_prop_at(store); + if !ctor_val.is_object() { + return object_proto; + } + let ctor_ptr = ctor_val.as_js_object_ptr(); + // SAFETY: ctor_ptr 是 Worker 构造器对象,存活。 + let ctor_obj = unsafe { &*ctor_ptr }; + let si_prototype = vm.perm_intern("prototype"); + let Some(proto_store) = vm.get_own_property_slot(ctor_obj, si_prototype) else { + return object_proto; + }; + let proto_val = ctor_obj.get_prop_at(proto_store); + if proto_val.is_object() { + proto_val + } else { + object_proto + } +} + +/// 读 `this` 的 `workerId` 自有属性(快路径:`get_own_property_slot` + `get_prop_at`)。 +/// +/// # 边界 +/// - `this` 非对象或缺 `workerId` 自有属性时抛 TypeError。 +/// - `workerId` 数值转 `u64`(非有限数值或负数归 0)。 +fn read_worker_id(vm: &mut Vm, this_val: JsValue) -> Result { + if !this_val.is_object() { + return Err(oxide_builtins::error::create_type_error(vm, "worker method called on non-object")); + } + let this_obj = unsafe { &*this_val.as_js_object_ptr() }; + let si_worker_id = vm.perm_intern("workerId"); + let Some(store) = vm.get_own_property_slot(this_obj, si_worker_id) else { + return Err(oxide_builtins::error::create_type_error(vm, "workerId not found")); + }; + let id_val = this_obj.get_prop_at(store); + let n = vm + .coerce_number_bounded(id_val) + .map_err(|e| oxide_builtins::error::create_type_error(vm, &e))?; + Ok(n.max(0.0) as u64) +} + +/// `worker.postMessage(msg)`:读 `workerId`、detach 消息、投递到 worker。 +/// +/// # 步骤 +/// 1. 读 `this`(`args[0]`)的 `workerId` 自有属性(快路径)。 +/// 2. 读 `args[1]` 为 msg,经 `detach_message` 转 `MessageValue`(空 transfer 集合)。 +/// 3. 委托 `vm.worker_post_message(id, mv)`(935.3 固有方法,内部 `send`)。 +/// +/// # 边界 +/// - DataCloneError 经 `detach_message` 的 `Err` 臂抛出。 +/// - worker 通道已断开(worker 线程退出)时 `vm.worker_post_message` 返 `Err`。 +pub(crate) fn worker_post_message(vm: &mut Vm, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let id = match read_worker_id(vm, this_val) { + Ok(id) => id, + Err(e) => return NativeResult::Err(e), + }; + let msg_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let transfer: std::collections::HashSet<*const JsObject> = std::collections::HashSet::new(); + let mv = match detach_message(vm, msg_val, &transfer) { + Ok(mv) => mv, + Err(e) => return NativeResult::Err(e), + }; + match vm.worker_post_message(id, mv) { + Ok(()) => NativeResult::Ok(JsValue::undefined()), + Err(e) => NativeResult::Err(oxide_builtins::error::create_type_error(vm, &e)), + } +} + +/// `worker.terminate()`:读 `workerId`、委托 `vm.worker_terminate(id)`(置 terminate 位并 join)。 +/// +/// # 边界 +/// - `id` 不存在时 `vm.worker_terminate` 返 `Err`。 +pub(crate) fn worker_terminate(vm: &mut Vm, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let id = match read_worker_id(vm, this_val) { + Ok(id) => id, + Err(e) => return NativeResult::Err(e), + }; + match vm.worker_terminate(id) { + Ok(()) => NativeResult::Ok(JsValue::undefined()), + Err(e) => NativeResult::Err(oxide_builtins::error::create_type_error(vm, &e)), + } +} + +/// `self.postMessage(msg)`(worker 侧):detach 消息、发回主线程。 +/// +/// # 步骤 +/// 1. 读 `args[1]` 为 msg,经 `detach_message` 转 `MessageValue`(空 transfer 集合)。 +/// 2. 经 thread-local `WORKER_OUT_TX` 发回主线程(主 realm 无通道时静默丢弃)。 +/// +/// # 边界 +/// - DataCloneError 经 `detach_message` 的 `Err` 臂抛出。 +/// - 主 realm(无 `WORKER_OUT_TX`)或主线程已 drop 接收端时静默丢弃。 +pub(crate) fn self_post_message(vm: &mut Vm, args: &[u8]) -> NativeResult { + let msg_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let transfer: std::collections::HashSet<*const JsObject> = std::collections::HashSet::new(); + let mv = match detach_message(vm, msg_val, &transfer) { + Ok(mv) => mv, + Err(e) => return NativeResult::Err(e), + }; + WORKER_OUT_TX.with(|slot| { + if let Some(tx) = slot.borrow().as_ref() { + let _ = tx.send(mv); + } + }); + NativeResult::Ok(JsValue::undefined()) +} + +/// `self.close()`(worker 侧):置位自关停请求(事件循环每轮检查后退出)。 +/// +/// # 副作用 +/// - 置位本线程的 `WORKER_CLOSE_REQUESTED`(事件循环下一轮检查后退出)。 +pub(crate) fn self_close(_vm: &mut Vm, _args: &[u8]) -> NativeResult { + request_worker_close(); + NativeResult::Ok(JsValue::undefined()) +} + +/// `self.name` getter(worker 侧):返空串(首版占位,规范面对齐留后续)。 +#[expect(dead_code)] +pub(crate) fn self_name(vm: &mut Vm, _args: &[u8]) -> NativeResult { + NativeResult::Ok(vm.new_string("")) +} + +/// `self.location` getter(worker 侧):返空串(首版占位,规范面对齐留后续)。 +#[expect(dead_code)] +pub(crate) fn self_location(vm: &mut Vm, _args: &[u8]) -> NativeResult { + NativeResult::Ok(vm.new_string("")) +} + +/// `MessageEvent` 构造辅助:建 MessageEvent 对象(`data` 属性 + `type` 属性)。 +/// +/// # 步骤 +/// 1. 建 PLAIN 对象(`[[Prototype]]` → `%Object.prototype%`)。 +/// 2. define `data` 属性(rehydrate 后的消息值,可枚举)。 +/// 3. define `type` 属性(事件类型串,可枚举)。 +/// 4. 返回对象。 +/// +/// # 边界与前提 +/// - `data` 是 rehydrate 后的消息值(GC 边,经 `alloc_object` 入对象表即成根)。 +/// - `type` 是事件类型串(`"message"`/`"error"`/`"messageerror"`)。 +/// +/// # 副作用 +/// - 新建一个 MessageEvent 对象(经 `alloc_object` 入对象表)。 +#[expect(dead_code)] // 供 935.5 主线程事件循环消费(onmessage 交付),935.4 未接线 +pub(crate) fn message_event_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { + let data_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let type_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() }; + + let object_proto = JsValue::from_js_object(vm.builtin_proto(ProtoKind::ObjectProto)); + let obj = JsObject::new_empty(EMPTY_SHAPE_ID, object_proto); + let ptr = vm.alloc_object(obj); + // SAFETY: ptr 是本函数刚 alloc_object 的对象,存活且本段无别名。 + let event_obj = unsafe { &mut *ptr }; + + let si_data = vm.perm_intern("data"); + let _ = vm.define_data_property(event_obj, si_data, data_val, PropAttributes::DEFAULT_DATA); + let si_type = vm.perm_intern("type"); + let _ = vm.define_data_property(event_obj, si_type, type_val, PropAttributes::DEFAULT_DATA); + + NativeResult::Ok(JsValue::from_js_object(ptr)) +} diff --git a/crates/oxide_vm/src/worker/mod.rs b/crates/oxide_vm/src/worker/mod.rs index 9968f9bb..c6edaef0 100644 --- a/crates/oxide_vm/src/worker/mod.rs +++ b/crates/oxide_vm/src/worker/mod.rs @@ -11,6 +11,8 @@ //! - worker 脚本编译失败不 panic:经 worker → 主线程通道上报错误串,worker 继续 //! 事件循环(可被干净终止)。 +pub mod bindings; + use std::collections::HashSet; use std::sync::Arc; use std::thread::JoinHandle; @@ -77,6 +79,8 @@ fn worker_event_loop( ) { let mut vm = Vm::with_kernel_core(core); vm.set_compiler_service(Arc::clone(&compiler)); + // 注入 worker → 主线程输出通道(self.postMessage 经 thread-local 发回主线程)。 + bindings::set_worker_out_tx(out_tx.clone()); // 编译 worker 脚本(worker 的程序)。失败不 panic:上报错误串后继续循环。 let script_module = match compiler.compile_script(&script) { @@ -98,6 +102,10 @@ fn worker_event_loop( // 事件循环:recv_timeout 驱动,超时查断开。 loop { + // 自关停请求(self.close()):事件循环每轮检查,置位即退出。 + if bindings::worker_close_requested() { + break; + } match rx.recv_timeout(Duration::from_millis(100)) { Ok(WorkerMail::Message(value)) => { // rehydrate → execute_task → drain_microtasks,再回显处理值。