mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 10:41:11 +08:00
556 lines
25 KiB
Rust
556 lines
25 KiB
Rust
//! Worker API 的 concrete `&mut Vm` native 函数:构造器、`postMessage`/`terminate`
|
||
//! 原型方法、`self` 面(`self.postMessage`/`self.close`/`self.name`/`self.location`)、
|
||
//! `importScripts` 与 `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, parse_transfer_list};
|
||
use oxide_kernel::message_queue::Sender;
|
||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||
use oxide_runtime_api::{ListenerEntry, NativeResult, ProtoKind, VmHost};
|
||
use oxide_types::object::{JsObject, PropAttributes};
|
||
use oxide_types::value::JsValue;
|
||
|
||
use crate::vm::Vm;
|
||
|
||
use super::WorkerOutMail;
|
||
|
||
// worker → 主线程输出通道(worker realm 专用,`worker_event_loop` 建 Vm 后注入)。
|
||
// thread-local:worker 线程独占,不跨线程共享。主 realm 的 Vm 不注入(恒 `None`)。
|
||
thread_local! {
|
||
static WORKER_OUT_TX: RefCell<Option<Sender<WorkerOutMail>>> = const { RefCell::new(None) };
|
||
}
|
||
|
||
// worker 自关停请求位(`self.close()` 置位,事件循环每轮检查)。
|
||
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 后调用)。
|
||
///
|
||
/// # 副作用
|
||
/// - 覆盖本线程的 `WORKER_OUT_TX`(worker 线程内恰好一次)。
|
||
pub(crate) fn set_worker_out_tx(tx: Sender<WorkerOutMail>) {
|
||
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())
|
||
}
|
||
|
||
/// 注入 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. 读 `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 家族。
|
||
/// 7. 返回 Worker 对象。
|
||
///
|
||
/// # 边界与前提
|
||
/// - `url` 是 worker 脚本的文件路径(主线程侧读取)。
|
||
/// - Worker 对象经 `alloc_object` 入对象表即成 GC 根;`workerId` 是数值(无 GC 边)。
|
||
///
|
||
/// # 副作用
|
||
/// - 派生一个 OS 线程(`spawn_worker`);登记一个 `WorkerHandle` 与一条
|
||
/// Worker 对象注册表条目(GC 根)。
|
||
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")),
|
||
};
|
||
|
||
// 读 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,
|
||
Err(e) => {
|
||
return NativeResult::Err(oxide_builtins::error::create_type_error(
|
||
vm,
|
||
&format!("failed to read worker script: {e}"),
|
||
))
|
||
}
|
||
};
|
||
|
||
// 派生 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)),
|
||
};
|
||
|
||
// 建 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 不在对象上建自有属性:改 Worker.prototype
|
||
// 访问器属性(getter 读注册表属性条目、setter 写),经既有 dispatchEvent 路径交付。
|
||
|
||
// 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),
|
||
);
|
||
|
||
// 登记 Worker 对象进注册表(GC 根,主线程事件循环据编号反查 onmessage)。
|
||
vm.worker_objects.insert(id, JsValue::from_js_object(ptr));
|
||
|
||
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<u64, JsValue> {
|
||
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, transfer)`:读 `workerId`、解析 transfer、detach 消息、投递到 worker。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 读 `this`(`args[0]`)的 `workerId` 自有属性(快路径)。
|
||
/// 2. 读 `args[1]` 为 msg、`args[2]` 为 transfer,经 `parse_transfer_list` 解析
|
||
/// transfer 集合(undefined 返空集、违例报 DataCloneError)。
|
||
/// 3. 经 `detach_message` 转 `MessageValue`(transfer 命中的缓冲源 detach)。
|
||
/// 4. 委托 `vm.worker_post_message(id, mv)`(935.3 固有方法,内部 `send`)。
|
||
///
|
||
/// # 边界
|
||
/// - DataCloneError 经 `parse_transfer_list` 与 `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_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
let transfer = match parse_transfer_list(vm, transfer_val) {
|
||
Ok(s) => s,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
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, transfer)`(worker 侧):解析 transfer、detach 消息、发回主线程。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 读 `args[1]` 为 msg、`args[2]` 为 transfer,经 `parse_transfer_list` 解析
|
||
/// transfer 集合(undefined 返空集、违例报 DataCloneError)。
|
||
/// 2. 经 `detach_message` 转 `MessageValue`(transfer 命中的缓冲源 detach)。
|
||
/// 3. 经 thread-local `WORKER_OUT_TX` 发回主线程(主 realm 无通道时静默丢弃)。
|
||
///
|
||
/// # 边界
|
||
/// - DataCloneError 经 `parse_transfer_list` 与 `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_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
let transfer = match parse_transfer_list(vm, transfer_val) {
|
||
Ok(s) => s,
|
||
Err(e) => return NativeResult::Err(e),
|
||
};
|
||
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(WorkerOutMail::Message(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())
|
||
}
|
||
|
||
/// `importScripts(...urls)`(worker 全局):逐 URL 同步读文件、编译、执行。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 逐个 URL 实参(`args[1..]`):读文件(`std::fs::read_to_string`,失败抛
|
||
/// TypeError)。
|
||
/// 2. 经 `create_plain_dynamic_script` 编译(失败抛 SyntaxError)。
|
||
/// 3. 经 `call_function_sync` 以 global 为 this 同步执行(运行异常原值透传)。
|
||
/// 4. 全部成功后返回 undefined。
|
||
///
|
||
/// # 边界与前提
|
||
/// - URL 是脚本文件路径(worker 线程侧读取,与 worker 脚本同一线程)。
|
||
/// - 主 realm 的 Vm 未注入编译服务时编译抛「未安装编译服务」错误(无害占位,
|
||
/// 与 self 侧 postMessage/close 在主 realm 的占位同一模式)。
|
||
///
|
||
/// # 副作用
|
||
/// - 每个 URL 扩展当前代际平表(动态脚本模块)并同步执行一段脚本
|
||
/// (顶层 var/function 声明落全局对象)。
|
||
pub(crate) fn import_scripts(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||
for i in 1..args.len() {
|
||
let url_val = vm.reg(args[i]);
|
||
let url = match vm.lookup_str(url_val) {
|
||
Some(u) => u,
|
||
None => {
|
||
return NativeResult::Err(oxide_builtins::error::create_type_error(
|
||
vm,
|
||
"importScripts url must be a string",
|
||
))
|
||
}
|
||
};
|
||
|
||
// 读文件(脚本源码)。
|
||
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 importScripts script: {e}"),
|
||
))
|
||
}
|
||
};
|
||
|
||
// 动态路径源契约:源码域转义形态传源(Rust String 无孤立 surrogate,
|
||
// 转义为恒等映射)。
|
||
let units: Vec<u16> = source.encode_utf16().collect();
|
||
let code = oxide_kernel::string_forge::source_escape(&units);
|
||
|
||
// 编译(失败抛 SyntaxError)。
|
||
let func = match vm.create_plain_dynamic_script(&code) {
|
||
Ok(f) => f,
|
||
Err(e) => return NativeResult::Err(oxide_builtins::error::create_syntax_error(vm, &e)),
|
||
};
|
||
|
||
// 以 global 为 this 同步执行(运行异常原值透传)。
|
||
let global = JsValue::from_js_object(vm.realm.session.borrow().global_object().as_ptr() as *mut JsObject);
|
||
if let Err(e) = vm.call_function_sync(func, global, &[]) {
|
||
// 原异常值在 last_uncaught_value(Err 文本是展平形态);槽空时
|
||
// (内部不变量违例)按文本恢复错误对象兜底。
|
||
return NativeResult::Err(
|
||
vm.take_uncaught_value()
|
||
.unwrap_or_else(|| oxide_builtins::error::create_from_text(vm, &e)),
|
||
);
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::undefined())
|
||
}
|
||
|
||
/// `self.name` getter(worker 侧):读本线程 worker 名称(未置位时返空串)。
|
||
pub(crate) fn self_name(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||
let name = WORKER_NAME.with(|slot| slot.borrow().as_deref().unwrap_or("").to_string());
|
||
NativeResult::Ok(vm.new_string(&name))
|
||
}
|
||
|
||
/// `self.location` getter(worker 侧):读本线程 worker 位置(未置位时返空串)。
|
||
pub(crate) fn self_location(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||
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` →
|
||
/// `"message"`)。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 读 callee 槽(寄存器 254)为当前函数对象(native 调用收口置位)。
|
||
/// 2. 读函数对象 `name` 自有属性,剥 `get ` / `set ` 前缀与 `on` 前缀得事件类型串。
|
||
/// 3. intern 事件类型串返 si。
|
||
///
|
||
/// # 边界
|
||
/// - callee 非对象、无 `name` 属性、或非 `get on<type>` / `set on<type>` 形态时返 `None`。
|
||
fn read_event_type_si(vm: &mut Vm) -> Option<u32> {
|
||
let callee = vm.reg(254);
|
||
if !callee.is_object() {
|
||
return None;
|
||
}
|
||
let callee_ptr = callee.as_js_object_ptr();
|
||
// SAFETY: callee 是当前函数对象,native 调用期间存活。
|
||
let callee_obj = unsafe { &*callee_ptr };
|
||
let si_name = vm.perm_intern("name");
|
||
let store = vm.get_own_property_slot(callee_obj, si_name)?;
|
||
let name_val = callee_obj.get_prop_at(store);
|
||
let name = vm.lookup_str(name_val)?;
|
||
let rest = name.strip_prefix("get ").or_else(|| name.strip_prefix("set "))?;
|
||
let type_text = rest.strip_prefix("on")?;
|
||
Some(vm.perm_intern(type_text))
|
||
}
|
||
|
||
/// `onmessage` / `onerror` / `onmessageerror` 访问器 getter(Worker.prototype 与
|
||
/// worker global 共用):读注册表属性条目返回调,无则 null。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 读 `this`(`args[0]`)为目标对象(非对象返 null)。
|
||
/// 2. 从函数对象 `name` 读事件类型 si。
|
||
/// 3. 查注册表取该目标同类型属性监听器条目,返其回调(无则 null)。
|
||
///
|
||
/// # 边界
|
||
/// - 注册表条目按目标对象裸指针为键(弱引用,不保活)。
|
||
pub(crate) fn worker_event_handler_getter(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||
let this_val = vm.reg(args[0]);
|
||
if !this_val.is_object() {
|
||
return NativeResult::Ok(JsValue::null());
|
||
}
|
||
let this_ptr = this_val.as_js_object_ptr();
|
||
let Some(type_si) = read_event_type_si(vm) else {
|
||
return NativeResult::Ok(JsValue::null());
|
||
};
|
||
for entry in vm.et_lookup(this_ptr) {
|
||
if entry.is_attribute && entry.type_si == type_si {
|
||
return NativeResult::Ok(entry.callback);
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::null())
|
||
}
|
||
|
||
/// `onmessage` / `onerror` / `onmessageerror` 访问器 setter(Worker.prototype 与
|
||
/// worker global 共用):写注册表属性条目(可调用则登记、null / undefined 则移除)。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 读 `this`(`args[0]`)为目标对象(非对象 no-op)。
|
||
/// 2. 从函数对象 `name` 读事件类型 si。
|
||
/// 3. 移除该目标同类型的既有属性监听器条目(若有)。
|
||
/// 4. 值为可调用时登记新属性监听器条目(`is_attribute` 置位);null / undefined 不登记。
|
||
///
|
||
/// # 副作用
|
||
/// - 写注册表(`et_unregister` / `et_register`)。
|
||
pub(crate) fn worker_event_handler_setter(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||
let this_val = vm.reg(args[0]);
|
||
if !this_val.is_object() {
|
||
return NativeResult::Ok(JsValue::undefined());
|
||
}
|
||
let this_ptr = this_val.as_js_object_ptr();
|
||
let Some(type_si) = read_event_type_si(vm) else {
|
||
return NativeResult::Ok(JsValue::undefined());
|
||
};
|
||
let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
// 移除该目标同类型的既有属性监听器条目(若有)。
|
||
for entry in vm.et_lookup(this_ptr) {
|
||
if entry.is_attribute && entry.type_si == type_si {
|
||
vm.et_unregister(this_ptr, entry.type_si, entry.callback, entry.capture);
|
||
}
|
||
}
|
||
// 值为可调用时登记新属性监听器条目。
|
||
if oxide_builtins::iterator::is_callable(value) {
|
||
vm.et_register(
|
||
this_ptr,
|
||
ListenerEntry {
|
||
type_si,
|
||
callback: value,
|
||
capture: false,
|
||
once: false,
|
||
is_attribute: true,
|
||
},
|
||
);
|
||
}
|
||
NativeResult::Ok(JsValue::undefined())
|
||
}
|
||
|
||
/// 消息事件构造辅助:建真实 MessageEvent 盒(`data` + `type`,`ports` 空数组)。
|
||
///
|
||
/// 消息路径(`deliver_worker_messages` / `deliver_self_message`)经本辅助建
|
||
/// 真实 MessageEvent,`e instanceof MessageEvent` 与 `e instanceof Event` 均
|
||
/// 成立,`e.data` 经原型 getter 读盒字段。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 读 `data`(`args[1]`)与 `type`(`args[2]`)。
|
||
/// 2. 委托 `create_message_event_box` 建真实 MessageEvent 盒(`origin` 空串、
|
||
/// `lastEpoch` 0、`source` null、`ports` 空数组,`[[Prototype]]` →
|
||
/// `MessageEvent.prototype`),使 `dispatchEvent` 品牌守卫通过。
|
||
/// 3. 返回对象。
|
||
///
|
||
/// # 边界与前提
|
||
/// - `data` 是 rehydrate 后的消息值(GC 边,经盒边入 mark)。
|
||
/// - `type` 是事件类型串(`"message"`)。
|
||
///
|
||
/// # 副作用
|
||
/// - 新建一个 MessageEvent 盒对象(经 `alloc_object` 入对象表)。
|
||
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() };
|
||
|
||
// 建真实 MessageEvent 盒(type 取 type_val 的字符串形态)。
|
||
let type_text = vm.lookup_str(type_val).unwrap_or_default();
|
||
let ptr = oxide_builtins::event::create_message_event_box(vm, data_val, &type_text);
|
||
NativeResult::Ok(JsValue::from_js_object(ptr))
|
||
}
|
||
|
||
/// 错误事件构造辅助:建真实 ErrorEvent 盒(`message` 为错误串、`filename`
|
||
/// 空串、`lineno` 与 `colno` 为 0、`error` 为 null)。
|
||
///
|
||
/// 错误路径(`deliver_error_event`)经本辅助建真实 ErrorEvent,
|
||
/// `e instanceof ErrorEvent` 与 `e instanceof Event` 均成立,`e.message`
|
||
/// 经原型 getter 读盒字段。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 读 `type`(`args[1]`)与 `message`(`args[2]`)。
|
||
/// 2. 委托 `create_error_event_box` 建真实 ErrorEvent 盒(`[[Prototype]]` →
|
||
/// `ErrorEvent.prototype`),使 `dispatchEvent` 品牌守卫通过。
|
||
/// 3. 返回对象。
|
||
///
|
||
/// # 边界与前提
|
||
/// - `type` 是事件类型串(`"error"`/`"messageerror"`)。
|
||
/// - `message` 是错误消息串(GC 边,经盒边入 mark)。
|
||
///
|
||
/// # 副作用
|
||
/// - 新建一个 ErrorEvent 盒对象(经 `alloc_object` 入对象表)。
|
||
pub(crate) fn build_error_event(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||
let type_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let message_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
|
||
// 建真实 ErrorEvent 盒(type 与 message 取实参的字符串形态)。
|
||
let type_text = vm.lookup_str(type_val).unwrap_or_default();
|
||
let message_text = vm.lookup_str(message_val).unwrap_or_default();
|
||
let ptr = oxide_builtins::event::create_error_event_box(vm, &message_text, &type_text);
|
||
NativeResult::Ok(JsValue::from_js_object(ptr))
|
||
}
|