feat: ErrorEvent 载荷盒加 GC 加构造器加原型加 onerror 整合

This commit is contained in:
2026-10-10 16:32:58 +00:00
parent 6214e9e93a
commit 21932522cf
10 changed files with 592 additions and 66 deletions
Generated
+19 -19
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"chrono",
"fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1227,7 +1227,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1236,7 +1236,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1256,7 +1256,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1270,7 +1270,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1285,7 +1285,7 @@ dependencies = [
[[package]]
name = "oxide_engine"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1297,7 +1297,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1306,7 +1306,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"bumpalo",
"dashmap",
@@ -1320,7 +1320,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1332,7 +1332,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"tracing",
"tracing-appender",
@@ -1341,7 +1341,7 @@ dependencies = [
[[package]]
name = "oxide_parser"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1356,7 +1356,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1372,7 +1372,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"num-bigint",
"num-traits",
@@ -1385,7 +1385,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1403,7 +1403,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"bumpalo",
"dashmap",
@@ -1416,7 +1416,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.14.12"
version = "0.14.13"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -23,7 +23,7 @@ members = [
]
[workspace.package]
version = "0.14.12"
version = "0.14.13"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+358 -7
View File
@@ -1,12 +1,15 @@
//! Event 基类与 MessageEvent 派生类行为面:`Event` 构造器(`new Event(type,
//! init)`)、原型 getter(type / target / currentTarget / eventPhase /
//! isTrusted / bubbles / cancelable / defaultPrevented / composed)与原型方法
//! (preventDefault / stopPropagation / stopImmediatePropagation /
//! composedPath);`MessageEvent` 构造器(`new MessageEvent(type, init)`)与
//! 原型 getter(data / origin / lastEpoch / source / ports)。
//! Event 基类与 MessageEvent / ErrorEvent 派生类行为面:`Event` 构造器
//! (`new Event(type, init)`)、原型 getter(type / target / currentTarget /
//! eventPhase / isTrusted / bubbles / cancelable / defaultPrevented /
//! composed)与原型方法(preventDefault / stopPropagation /
//! stopImmediatePropagation / composedPath);`MessageEvent` 构造器
//! (`new MessageEvent(type, init)`)与原型 getter(data / origin / lastEpoch /
//! source / ports);`ErrorEvent` 构造器(`new ErrorEvent(type, init)`)与
//! 原型 getter(message / filename / lineno / colno / error)。
//!
//! 事件载荷盒(type 串与九项状态位,MessageEvent 加 data / origin /
//! lastEpoch / source / ports 五字段)存于对象 `native_fn` 槽,向 GC 家族表
//! lastEpoch / source / ports 五字段,ErrorEvent 加 message / filename /
//! lineno / colno / error 五字段)存于对象 `native_fn` 槽,向 GC 家族表
//! 暴露 edges/size/drop 三自由函数。
use std::mem;
@@ -54,6 +57,22 @@ struct MessageEventInner {
ports: Vec<JsValue>,
}
/// ErrorEvent 载荷盒:事件基类字段(`EventInner` 嵌入,位于偏移 0)加错误
/// 五字段(message / filename / lineno / colno / error)。存于对象
/// `native_fn` 槽(`Box::into_raw`,realm 局部单线程)。
///
/// `#[repr(C)]` 固定 `base` 位于偏移 0:派生类盒指针转基类指针读首字段依赖该
/// 偏移稳定(与 `EventInner` 同型约束)。
#[repr(C)]
struct ErrorEventInner {
base: EventInner,
message: JsValue,
filename: JsValue,
lineno: u32,
colno: u32,
error: JsValue,
}
/// 是否事件系对象(Event / MessageEvent / ErrorEvent / CustomEvent 四标签)。
pub fn is_event_family_obj(obj: &JsObject) -> bool {
matches!(
@@ -882,3 +901,335 @@ pub fn message_event_ports_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeR
}
NativeResult::Ok(JsValue::from_js_object(arr))
}
// —— ErrorEvent 派生类 ——
/// 取 ErrorEvent 载荷盒指针:仅 ErrorEvent 标签对象返回 `Some`,其余标签
/// 返回 `None`。
fn error_event_payload_ptr(obj: &JsObject) -> Option<*mut ErrorEventInner> {
if obj.type_tag != JsObject::OBJ_TYPE_ERROR_EVENT {
return None;
}
obj.native_fn().map(|ptr| ptr.as_ptr() as *mut ErrorEventInner)
}
/// 收集 ErrorEvent 的引用边(基类 type / target / current_target 边加
/// message / filename / error 边)。
///
/// `target` / `current_target` 空窗口(构造期回填之前)为 null,边函数自守,
/// 语义自含。
pub fn error_event_native_edges(obj: &JsObject) -> Vec<JsValue> {
let Some(ptr) = error_event_payload_ptr(obj) else {
return Vec::new();
};
if ptr.is_null() {
return Vec::new();
}
// SAFETY: ptr 指向有效载荷盒(槽位仅构造期写入、释放时置空),只读字段。
let inner = unsafe { &*ptr };
let mut edges = vec![inner.base.r#type, inner.base.target, inner.base.current_target];
edges.push(inner.message);
edges.push(inner.filename);
edges.push(inner.error);
edges
}
/// 只读核算 ErrorEvent 载荷盒字节(不释放)。
pub fn error_event_native_size(obj: &JsObject) -> u64 {
let Some(ptr) = error_event_payload_ptr(obj) else {
return 0;
};
if ptr.is_null() {
return 0;
}
mem::size_of::<ErrorEventInner>() as u64
}
/// 释放 ErrorEvent 载荷盒(`Box<ErrorEventInner>`),返回释放字节数。
///
/// 盒 drop 时基类字段与 message / filename / error 的 JsValue 边随盒释放
/// (GC 已判定死对象);槽置空保证重复调用零释放(幂等)。
pub fn drop_error_event_native(obj: &mut JsObject) -> u64 {
let bytes = error_event_native_size(obj);
if bytes == 0 {
return 0;
}
let Some(ptr) = error_event_payload_ptr(obj) else {
return 0;
};
// SAFETY: ptr 非空(error_event_native_size 已验证),Box::from_raw 恰好
// 释放一次。
unsafe {
drop(Box::from_raw(ptr));
}
obj.set_native_fn(None);
bytes
}
/// 建一个 ErrorEvent 载荷盒对象(`type` 串与 `message` 串),返回对象指针。
///
/// 供交付路径(错误事件)建真实 ErrorEvent 盒,使 `dispatchEvent` 品牌守卫
/// 通过。盒的 `filename` 为空串、`lineno` 与 `colno` 为 0、`error` 为 null;
/// `[[Prototype]]` 指向 `ErrorEvent.prototype`(`e instanceof ErrorEvent` 与
/// `e instanceof Event` 均成立)。
///
/// # 副作用
/// - 新建一个 ErrorEvent 盒对象(经 `alloc_object` 入对象表)。
pub fn create_error_event_box<H: VmHost>(vm: &mut H, message: &str, type_str: &str) -> *mut JsObject {
let type_js = vm.new_string(type_str);
let message_js = vm.new_string(message);
let filename_js = vm.new_string("");
let inner = Box::into_raw(Box::new(ErrorEventInner {
base: EventInner {
r#type: type_js,
target: JsValue::null(),
current_target: JsValue::null(),
event_phase: 0,
is_trusted: false,
bubbles: false,
cancelable: false,
default_prevented: false,
propagation_stopped: false,
immediate_stopped: false,
composed: false,
},
message: message_js,
filename: filename_js,
lineno: 0,
colno: 0,
error: JsValue::null(),
}));
let ee_proto = JsValue::from_js_object(vm.builtin_proto(ProtoKind::ErrorEventProto));
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, ee_proto);
let ptr = vm.alloc_object(obj);
// SAFETY: ptr 是本函数刚 alloc_object 的对象,存活且本段无别名;native_fn 存
// 不透明 `Box<ErrorEventInner>` 指针,与 ArrayBuffer / RegExp 的类型化
// 存储模式一致。
let ee_obj = unsafe { &mut *ptr };
ee_obj.type_tag = JsObject::OBJ_TYPE_ERROR_EVENT;
ee_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(inner as *const ()) }));
ptr
}
/// ErrorEvent 构造器:`new ErrorEvent(type, init)`。
///
/// # 步骤
/// 1. 非构造形态(`!constructing_native`)抛 TypeError(HTML 规范只有
/// [[Construct]],无 [[Call]])
/// 2. type 经完整 ToString 转串;空串抛 SyntaxError
/// 3. init 字典读 message / filename / lineno / colno / error(缺省:message
/// 为空串、filename 为空串、lineno 为 0、colno 为 0、error 为 null)
/// 4. 建载荷盒入 receiver 的 `native_fn` 槽,置 type_tag
/// 5. 返回 receiver
///
/// # 边界与前提
/// - receiver(`args[0]`)是 VM 按构造器 prototype 新分配的对象,proto 已
/// 指向 %ErrorEvent.prototype%;
/// - init 缺省(undefined)或非对象时对应字段取缺省。
///
/// # 副作用
/// - receiver 的 `native_fn` 槽写入载荷盒、`type_tag` 置 ErrorEvent 标签。
pub fn error_event_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if !vm.constructing_native() {
return NativeResult::Err(crate::error::create_type_error(vm, "ErrorEvent constructor requires new"));
}
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "ErrorEvent constructor requires new"));
}
// type 经完整 ToString 转串(对象经 ToPrimitive,Symbol 抛 TypeError)。
let type_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let type_str = match oxide_runtime_api::to_string_full(type_val, vm) {
Ok(s) => s,
Err(_) => {
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a string"));
}
};
if type_str.is_empty() {
return NativeResult::Err(crate::error::create_syntax_error(vm, "The provided type is empty"));
}
// init 字典读 message / filename / lineno / colno / error(缺省或非对象
// 时取缺省)。
let mut message = JsValue::undefined();
let mut filename = JsValue::undefined();
let mut lineno = 0u32;
let mut colno = 0u32;
let mut error = JsValue::null();
if args.len() > 2 {
let init_val = vm.reg(args[2]);
if init_val.is_object() {
let init_ptr = init_val.as_js_object_ptr();
// SAFETY: init_val 已确认是对象值,借用即时消费。
let init_obj = unsafe { &*init_ptr };
// message / filename / error 直读属性(getter 副作用与异常原值传播)。
for (name, slot) in [("message", &mut message), ("filename", &mut filename), ("error", &mut error)] {
let si = vm.perm_intern(name);
match vm.ordinary_get(init_obj, si, init_val) {
Ok(v) => *slot = v,
Err(_) => {
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(
vm,
"ErrorEvent init property getter failed",
));
}
}
}
// lineno / colno 读数值(非有限非负数归 0)。
for (name, slot) in [("lineno", &mut lineno), ("colno", &mut colno)] {
let si = vm.perm_intern(name);
match vm.ordinary_get(init_obj, si, init_val) {
Ok(v) => {
let n = oxide_runtime_api::to_number(v);
*slot = if n.is_finite() && n >= 0.0 { n as u32 } else { 0 };
}
Err(_) => {
if let Some(exc) = vm.take_uncaught_value() {
return NativeResult::Err(exc);
}
return NativeResult::Err(crate::error::create_type_error(
vm,
"ErrorEvent init property getter failed",
));
}
}
}
}
}
// message / filename 缺省为空串、error 缺省为 null(init 缺属性时
// ordinary_get 返回 undefined,须归回缺省)。
if message.is_undefined() {
message = vm.new_string("");
}
if filename.is_undefined() {
filename = vm.new_string("");
}
if error.is_undefined() {
error = JsValue::null();
}
// 建载荷盒入 receiver 的 native_fn 槽,置 ErrorEvent 标签。
let type_js = vm.new_string(&type_str);
let inner = Box::into_raw(Box::new(ErrorEventInner {
base: EventInner {
r#type: type_js,
target: JsValue::null(),
current_target: JsValue::null(),
event_phase: 0,
is_trusted: false,
bubbles: false,
cancelable: false,
default_prevented: false,
propagation_stopped: false,
immediate_stopped: false,
composed: false,
},
message,
filename,
lineno,
colno,
error,
}));
// SAFETY: receiver 不可调用,native_fn 存不透明 `Box<ErrorEventInner>`
// 指针,与 ArrayBuffer / RegExp 的类型化存储模式一致。
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
obj.type_tag = JsObject::OBJ_TYPE_ERROR_EVENT;
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(inner as *const ()) }));
NativeResult::Ok(this_val)
}
/// 解析 this 为 ErrorEvent 载荷盒共享引用:非 ErrorEvent 对象或无载荷盒
/// 返回 None。
///
/// # 注意事项
/// 返回引用按 `'a` 声称为存活,实际存活至载荷盒被 GC 释放;调用方须在无 GC
/// 窗口的单 native 函数内消费,不得跨用户调用窗口持有。
fn error_event_inner_of<'a>(this_val: JsValue) -> Option<&'a ErrorEventInner> {
if !this_val.is_object() {
return None;
}
let obj_ptr = this_val.as_js_object_ptr();
if obj_ptr.is_null() {
return None;
}
// SAFETY: this_val 已确认是对象值,obj_ptr 指向合法 JsObject。
let obj = unsafe { &*obj_ptr };
let box_ptr = error_event_payload_ptr(obj)?;
if box_ptr.is_null() {
return None;
}
// SAFETY: box_ptr 指向有效载荷盒(构造期写入、释放时置空);'a 存活由
// 调用方「无 GC 窗口内消费」约定保证。
Some(unsafe { &*box_ptr })
}
/// `ErrorEvent.prototype.message`:返回错误消息串(缺省为空串)。
pub fn error_event_message_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let Some(inner) = error_event_inner_of(this_val) else {
return NativeResult::Err(crate::error::create_type_error(
vm,
"ErrorEvent.prototype.message getter called on non-ErrorEvent object",
));
};
NativeResult::Ok(inner.message)
}
/// `ErrorEvent.prototype.filename`:返回错误发生文件名(缺省为空串)。
pub fn error_event_filename_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let Some(inner) = error_event_inner_of(this_val) else {
return NativeResult::Err(crate::error::create_type_error(
vm,
"ErrorEvent.prototype.filename getter called on non-ErrorEvent object",
));
};
NativeResult::Ok(inner.filename)
}
/// `ErrorEvent.prototype.lineno`:返回错误发生行号(缺省为 0)。
pub fn error_event_lineno_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let Some(inner) = error_event_inner_of(this_val) else {
return NativeResult::Err(crate::error::create_type_error(
vm,
"ErrorEvent.prototype.lineno getter called on non-ErrorEvent object",
));
};
NativeResult::Ok(JsValue::int(inner.lineno as i32))
}
/// `ErrorEvent.prototype.colno`:返回错误发生列号(缺省为 0)。
pub fn error_event_colno_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let Some(inner) = error_event_inner_of(this_val) else {
return NativeResult::Err(crate::error::create_type_error(
vm,
"ErrorEvent.prototype.colno getter called on non-ErrorEvent object",
));
};
NativeResult::Ok(JsValue::int(inner.colno as i32))
}
/// `ErrorEvent.prototype.error`:返回错误对象(缺省为 null)。
pub fn error_event_error_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let Some(inner) = error_event_inner_of(this_val) else {
return NativeResult::Err(crate::error::create_type_error(
vm,
"ErrorEvent.prototype.error getter called on non-ErrorEvent object",
));
};
NativeResult::Ok(inner.error)
}
+79 -7
View File
@@ -1,10 +1,12 @@
//! Event 体系绑定:就地填充 `event_constructor` / `event_proto` 两枚 P 字段
//! 占位(构造器本体、原型方法、原型 getter 与 Event 常量),并绑全局 `Event`;
//! 就地填充 `message_event_constructor` / `message_event_proto` 两枚 P 字段
//! 占位(构造器本体与原型 getter),并绑全局 `MessageEvent`。
//! 占位(构造器本体与原型 getter),并绑全局 `MessageEvent`;就地填充
//! `error_event_constructor` / `error_event_proto` 两枚 P 字段占位(构造器
//! 本体与原型 getter),并绑全局 `ErrorEvent`。
//!
//! 九枚 P 字段中其余四枚(ErrorEvent / CustomEvent 两对)由后续子任务就地
//! 填充,本函数不读不写。
//! 九枚 P 字段中其余两枚(CustomEvent 一对)由后续子任务就地填充,本函数
//! 不读不写。
use std::sync::Arc;
@@ -14,11 +16,12 @@ use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue;
/// 把 Event 与 MessageEvent 构造器及原型方法绑定到 global。
/// 把 Event / MessageEvent / ErrorEvent 构造器及原型方法绑定到 global。
///
/// `event_constructor` / `event_proto` 与 `message_event_constructor` /
/// `message_event_proto` 四枚 P 字段是空对象占位,本函数就地填充(不新建
/// 对象——P 字段本身就是槽位,快照/脏检查/收尾枚举已覆盖)。
/// `event_constructor` / `event_proto`、`message_event_constructor` /
/// `message_event_proto` 与 `error_event_constructor` / `error_event_proto`
/// 六枚 P 字段是空对象占位,本函数就地填充(不新建对象——P 字段本身就是
/// 槽位,快照/脏检查/收尾枚举已覆盖)。
///
/// # 幂等
/// 本函数从三条路径到达:`init_kernel_builtins`(占位是空对象)、
@@ -239,6 +242,75 @@ pub fn bind_event(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
}
}
// ---- 填 ErrorEvent 构造器与原型占位 ----
let ee_ctor_ptr = world.error_event_constructor.as_ptr() as *mut JsObject;
let ee_ctor = unsafe { &mut *ee_ctor_ptr };
let ee_proto_ptr = world.error_event_proto.as_ptr() as *mut JsObject;
let ee_proto = unsafe { &mut *ee_proto_ptr };
// 构造器:function 标志、prototype 槽、name 槽、[[Prototype]] →
// Function.prototype(幂等,裸写槽)。
ee_ctor.set_function(true);
if sh.lookup_position(ee_ctor.shape_id(), si_prototype).is_none() {
let shape = sh.make_shape(ee_ctor.shape_id(), si_prototype);
ee_ctor.set_shape_id(shape);
let pos = ee_ctor.push_prop(JsValue::from_js_object(ee_proto_ptr));
ee_ctor.set_data_meta(pos, PropAttributes::new(false, false, false));
ee_ctor.bump_generation();
}
if sh.lookup_position(ee_ctor.shape_id(), si_name).is_none() {
let shape = sh.make_shape(ee_ctor.shape_id(), si_name);
ee_ctor.set_shape_id(shape);
ee_ctor
.ensure_hash_props()
.push(JsValue::perm_string(sf.string_ptr(sf.intern("ErrorEvent").0)));
let pos = ee_ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1);
ee_ctor.set_data_meta(pos, PropAttributes::new(false, false, true));
ee_ctor.bump_generation();
}
let _ = ee_ctor.set_proto(fn_proto_val);
configure_native_constructor(
ee_ctor,
oxide_builtins::event::error_event_constructor::<crate::vm::Vm> as *const (),
2,
);
bind_constructor!(
core,
global,
"ErrorEvent",
ee_ctor_ptr,
oxide_builtins::event::error_event_constructor::<crate::vm::Vm>,
2,
hash: true
);
// 原型:constructor 槽、[[Prototype]] → Event.prototype、五个 getter
// (message / filename / lineno / colno / error,每 getter 先
// lookup_position 守卫)。
if sh.lookup_position(ee_proto.shape_id(), si_constructor).is_none() {
let shape = sh.make_shape(ee_proto.shape_id(), si_constructor);
ee_proto.set_shape_id(shape);
let pos = ee_proto.push_prop(JsValue::from_js_object(ee_ctor_ptr));
ee_proto.set_data_meta(pos, PropAttributes::new(true, false, true));
ee_proto.bump_generation();
}
let _ = ee_proto.set_proto(event_proto_val);
let ee_getter_fns: [(&str, *const ()); 5] = [
("message", oxide_builtins::event::error_event_message_getter::<crate::vm::Vm> as *const ()),
("filename", oxide_builtins::event::error_event_filename_getter::<crate::vm::Vm> as *const ()),
("lineno", oxide_builtins::event::error_event_lineno_getter::<crate::vm::Vm> as *const ()),
("colno", oxide_builtins::event::error_event_colno_getter::<crate::vm::Vm> as *const ()),
("error", oxide_builtins::event::error_event_error_getter::<crate::vm::Vm> as *const ()),
];
for (name, fn_ptr) in ee_getter_fns {
let si = sf.intern(name).0;
if sh.lookup_position(ee_proto.shape_id(), si).is_none() {
bind_accessor_getter(core, session, ee_proto, name, fn_ptr);
}
}
// ---- 填 EventTarget 原型占位(无构造器,仅暴露原型与三方法)----
// 幂等:三方法经 lookup_position 守卫,[[Prototype]] 裸写槽幂等。
let et_proto_ptr = world.event_target_proto.as_ptr() as *mut JsObject;
+2 -1
View File
@@ -26,7 +26,8 @@ pub mod bind_date;
pub mod bind_disposable_stack;
/// Error 家族构造器与原型的 native 方法绑定(含各子类型构造器创建)。
pub mod bind_error;
/// Event 体系绑定入口(当前为空占位,九枚 P 字段由后续任务就地填充)。
/// Event 体系绑定入口(Event / MessageEvent / ErrorEvent 构造器与原型就地
/// 填充,CustomEvent 一对由后续任务填充)。
pub mod bind_event;
/// Function 构造器与原型的 native 方法绑定。
pub mod bind_function;
+10 -3
View File
@@ -248,9 +248,16 @@ pub(crate) fn ops_for(family: NativeBoxFamily) -> NativeBoxOps {
string_edges: None,
cell_edges: None,
},
// ErrorEvent / CustomEvent 载荷盒由后续子任务填充;本两臂当前无引用
// 边,仅 size/drop 链消费。
NativeBoxFamily::ErrorEvent | NativeBoxFamily::CustomEvent => NativeBoxOps {
// ErrorEvent 派生类载荷盒:基类 type / target / current_target 边加
// message / filename / error 边。
NativeBoxFamily::ErrorEvent => NativeBoxOps {
object_edges: Some(event::error_event_native_edges),
string_edges: None,
cell_edges: None,
},
// CustomEvent 载荷盒由后续子任务填充;本臂当前无引用边,仅 size/drop
// 链消费。
NativeBoxFamily::CustomEvent => NativeBoxOps {
object_edges: None,
string_edges: None,
cell_edges: None,
+5 -2
View File
@@ -134,6 +134,7 @@ impl SessionGc {
bytes += broadcast_channel::broadcast_channel_native_size(obj);
bytes += event::event_native_size(obj);
bytes += event::message_event_native_size(obj);
bytes += event::error_event_native_size(obj);
bytes += crate::arguments_gc::arguments_native_size(obj);
bytes
@@ -378,12 +379,13 @@ impl SessionGc {
}
}
// 事件系载荷盒(Event / MessageEvent / ErrorEvent / CustomEvent)字符串
// 边标记:按标签取对应边函数;ErrorEvent / CustomEvent 载荷盒由后续子
// 任务填充,当前取空边。
// 边标记:按标签取对应边函数;CustomEvent 载荷盒由后续子任务填充,
// 当前取空边。
if event::is_event_family_obj(obj) {
let edges = match obj.type_tag {
JsObject::OBJ_TYPE_EVENT => event::event_native_edges(obj),
JsObject::OBJ_TYPE_MESSAGE_EVENT => event::message_event_native_edges(obj),
JsObject::OBJ_TYPE_ERROR_EVENT => event::error_event_native_edges(obj),
_ => Vec::new(),
};
for value in edges {
@@ -593,6 +595,7 @@ impl SessionGc {
freed_bytes += broadcast_channel::drop_broadcast_channel_native(obj);
freed_bytes += event::drop_event_native(obj);
freed_bytes += event::drop_message_event_native(obj);
freed_bytes += event::drop_error_event_native(obj);
freed_bytes += crate::arguments_gc::drop_arguments_native(obj);
freed_bytes
+13 -22
View File
@@ -316,41 +316,32 @@ pub(crate) fn message_event_constructor(vm: &mut Vm, args: &[u8]) -> NativeResul
NativeResult::Ok(JsValue::from_js_object(ptr))
}
/// 错误事件构造辅助(临时形态):建真实 Event 盒(`type` 字段)加 `data` /
/// `message` 普通属性。
/// 错误事件构造辅助:建真实 ErrorEvent 盒(`message` 为错误串、`filename`
/// 空串、`lineno` 与 `colno` 为 0、`error` 为 null)。
///
/// 错误路径(`deliver_error_event`)经本辅助建错误事件:`data` 为 undefined、
/// `message` 为错误串,供 onerror 处理器读取。
/// 错误路径(`deliver_error_event`)经本辅助建真实 ErrorEvent,
/// `e instanceof ErrorEvent` 与 `e instanceof Event` 均成立,`e.message`
/// 经原型 getter 读盒字段。
///
/// # 步骤
/// 1. 读 `type`(`args[1]`)与 `message`(`args[2]`)。
/// 2. 经 `create_event_box` 建真实 Event 盒(`type` 取 `type_val` 的字符串
/// 形态,`[[Prototype]]` → `Event.prototype`),使 `dispatchEvent` 品牌守卫
/// 通过。
/// 3. define `data` 属性(undefined,可枚举)与 `message` 属性(错误消息串,
/// 可枚举)。
/// 4. 返回对象。
/// 2. 委托 `create_error_event_box` 建真实 ErrorEvent 盒(`[[Prototype]]` →
/// `ErrorEvent.prototype`),使 `dispatchEvent` 品牌守卫通过。
/// 3. 返回对象。
///
/// # 边界与前提
/// - `type` 是事件类型串(`"error"`/`"messageerror"`)。
/// - `message` 是错误消息串。
/// - `message` 是错误消息串(GC 边,经盒边入 mark)。
///
/// # 副作用
/// - 新建一个 Event 盒对象(经 `alloc_object` 入对象表)。
/// - 新建一个 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() };
// 建真实 Event 盒(type 取 type_val 的字符串形态)。
// 建真实 ErrorEvent 盒(type 与 message 取实参的字符串形态)。
let type_text = vm.lookup_str(type_val).unwrap_or_default();
let ptr = oxide_builtins::event::create_event_box(vm, &type_text);
// SAFETY: ptr 是 create_event_box 刚 alloc_object 的对象,存活且本段无别名。
let event_obj = unsafe { &mut *ptr };
let si_data = vm.perm_intern("data");
let _ = vm.define_data_property(event_obj, si_data, JsValue::undefined(), PropAttributes::DEFAULT_DATA);
let si_message = vm.perm_intern("message");
let _ = vm.define_data_property(event_obj, si_message, message_val, PropAttributes::DEFAULT_DATA);
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))
}
+48 -4
View File
@@ -396,7 +396,7 @@ impl Vm {
/// 交付错误事件:建事件、经 `dispatchEvent` 交付(注册表查监听器)。
///
/// # 步骤
/// 1. 建错误事件(`type` = `event_type`、`message` = `message`、`data` = undefined)。
/// 1. 建错误事件(`type` = `event_type`、`message` = `message`)。
/// 2. 经 `dispatch_event_on` 交付到 `worker_obj`,后 `drain_microtasks`。
///
/// # 边界与前提
@@ -437,8 +437,9 @@ impl Vm {
}
}
/// 错误路径建事件对象(临时形态):建真实 Event 盒(`type` 字段)加
/// `data` / `message` 普通属性,委托 `build_error_event` 构造辅助。
/// 错误路径建事件对象:建真实 ErrorEvent(`message` 为错误串、`filename`
/// 空串、`lineno` 与 `colno` 为 0、`error` 为 null),委托
/// `build_error_event` 构造辅助。
///
/// # 步骤
/// 1. `type` 与 `message` 串物化为 session 串。
@@ -449,7 +450,7 @@ impl Vm {
/// 新建的事件对象值(构造失败时返回错误对象值)。
///
/// # 副作用
/// - 新建一个 Event 盒对象(经 `alloc_object` 入对象表)。
/// - 新建一个 ErrorEvent 盒对象(经 `alloc_object` 入对象表)。
fn build_error_event(&mut self, type_str: &str, message: &str) -> JsValue {
let type_val = self.new_string(type_str);
let message_val = self.new_string(message);
@@ -829,6 +830,49 @@ mod tests {
let _ = std::fs::remove_file(&worker_path);
}
/// 端到端:worker 脚本编译失败,主线程 onerror 收到真实 ErrorEvent
/// (e instanceof ErrorEvent 与 e instanceof Event 均真)。
#[test]
fn worker_e2e_onerror_is_error_event() {
let mut vm = vm_with_compiler();
// worker 脚本:语法错误(编译失败)。
let worker_path = write_worker_script("function { 语法错误");
// 主脚本:建 Worker、设 addEventListener('error'),记录 instanceof 结果。
let main_script = format!(
"var w = new Worker('{}'); w.addEventListener('error', function(e) {{ globalThis.isEE = e instanceof ErrorEvent; globalThis.isE = e instanceof Event; }});",
worker_path.display()
);
vm.run(&Arc::new(compile_script(&main_script))).expect("主脚本应运行");
// 事件循环轮询至 globalThis.isEE 被设置(带截止,防 flaky)。
let deadline = Instant::now() + Duration::from_secs(5);
loop {
vm.deliver_worker_messages();
vm.cleanup_disconnected_workers();
if global_value_opt(&vm, "isEE").is_some() {
break;
}
if Instant::now() >= deadline {
panic!("5 秒内 onerror 未被调用");
}
std::thread::sleep(Duration::from_millis(10));
}
assert_eq!(
global_value_opt(&vm, "isEE"),
Some(JsValue::bool(true)),
"事件应 instanceof ErrorEvent"
);
assert_eq!(
global_value_opt(&vm, "isE"),
Some(JsValue::bool(true)),
"事件应 instanceof Event"
);
vm.shutdown_workers();
let _ = std::fs::remove_file(&worker_path);
}
/// onmessageerror 交付辅助:无自然触发,直接单测交付辅助(handler 被调用、
/// 事件 type 为 "messageerror")。不钉触发可达性(当前架构不可达)。
#[test]
@@ -237,3 +237,60 @@ fn registry_pruned_after_target_collected() {
"ok"
);
}
/// ErrorEvent 构造器:init 的 message 经原型 getter 可读。
#[test]
fn error_event_constructor_message() {
assert_eq!(
eval("new ErrorEvent('error', { message: 'boom' }).message;"),
"boom"
);
}
/// ErrorEvent 缺省字段:message 空串、filename 空串、lineno 与 colno 为 0、
/// error 为 null。
#[test]
fn error_event_defaults() {
assert_eq!(
eval(
"var e = new ErrorEvent('error'); \
e.message === '' && e.filename === '' && e.lineno === 0 && e.colno === 0 && e.error === null;"
),
"true"
);
}
/// ErrorEvent 原型链:instanceof ErrorEvent 与 instanceof Event 均真,
/// 原型双亲为 Event.prototype。
#[test]
fn error_event_proto_chain() {
assert_eq!(
eval(
"var e = new ErrorEvent('error'); \
e instanceof ErrorEvent && e instanceof Event && \
Object.getPrototypeOf(e) === ErrorEvent.prototype && \
Object.getPrototypeOf(ErrorEvent.prototype) === Event.prototype;"
),
"true"
);
}
/// ErrorEvent 非构造形态抛 TypeError。
#[test]
fn error_event_requires_new() {
assert_eq!(
eval("try { ErrorEvent('error'); 'no-throw'; } catch (e) { e instanceof TypeError; }"),
"true"
);
}
/// ErrorEvent 盒边 GC 保活:error 字段引用对象经完整收集后仍存活。
#[test]
fn error_event_box_edge_survives_gc() {
let mut vm = vm_with_script(
"globalThis.e = new ErrorEvent('error', { error: { tag: 1 } });",
);
// 强制完整收集:error 字段是盒内 GC 边,对象应保活。
vm.collect_session_gc();
assert_eq!(vm_eval(&mut vm, "globalThis.e.error.tag;"), "1");
}