mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: worker 全局 importScripts 逐 URL 同步读文件编译执行
This commit is contained in:
Generated
+19
-19
@@ -1160,7 +1160,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_builtins"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fast-float",
|
||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_bytecode"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_log",
|
||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cfg"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_emit",
|
||||
@@ -1204,7 +1204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cli"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
@@ -1227,7 +1227,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_code_cache"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"oxide_bytecode",
|
||||
@@ -1236,7 +1236,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_compiler"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1256,7 +1256,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_dce"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1270,7 +1270,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
@@ -1285,7 +1285,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_engine"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1297,7 +1297,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_log",
|
||||
@@ -1306,7 +1306,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_kernel"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1320,7 +1320,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_liveness"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1332,7 +1332,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_log"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@@ -1341,7 +1341,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_parser"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -1356,7 +1356,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_regalloc"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1372,7 +1372,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_runtime_api"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -1385,7 +1385,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_test262"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1403,7 +1403,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_types"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1416,7 +1416,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_vm"
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.14.17"
|
||||
version = "0.14.18"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -28,8 +28,9 @@ use oxide_types::value::JsValue;
|
||||
/// # 副作用
|
||||
/// - 构造器与原型对象经 `Box::into_raw` 登记 world 释放登记表(session 收尾统一
|
||||
/// 释放);全局 `Worker` 槽经 `bind_constructor!` 原位安装。
|
||||
/// - self 面方法(`postMessage`/`close` 方法、`name`/`location` getter)绑 global
|
||||
/// (worker realm 经 `self` === global 可达,主 realm 无 `self` 时为无害占位)。
|
||||
/// - self 面方法(`postMessage`/`close` 方法、`name`/`location` getter)与
|
||||
/// `importScripts` 绑 global(worker realm 经 `self` === global 可达,
|
||||
/// 主 realm 无 `self` 时为无害占位)。
|
||||
///
|
||||
/// `realm_id` 未使用(Worker 无 well-known 符号键),保留签名与其他绑定函数一致。
|
||||
pub fn bind_worker(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject, _realm_id: u32) {
|
||||
@@ -166,6 +167,21 @@ pub fn bind_worker(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
],
|
||||
);
|
||||
}
|
||||
// importScripts(worker 全局,逐 URL 同步读文件、编译、执行;主 realm 为无害占位)。
|
||||
// 幂等守卫。
|
||||
let si_import_scripts = sf.intern("importScripts").0;
|
||||
if sh.lookup_position(global.shape_id(), si_import_scripts).is_none() {
|
||||
apply_binding_table(
|
||||
world,
|
||||
global,
|
||||
core,
|
||||
&[(
|
||||
"importScripts",
|
||||
crate::worker::bindings::import_scripts as *const (),
|
||||
1,
|
||||
)],
|
||||
);
|
||||
}
|
||||
// EventTarget 三方法直绑全局(worker realm 经 self === global 可达;全局原型链
|
||||
// 是 Object.prototype,首版不改全局原型,三方法直绑全局等价可见)。幂等守卫。
|
||||
let si_self_add = sf.intern("addEventListener").0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Worker API 的 concrete `&mut Vm` native 函数:构造器、`postMessage`/`terminate`
|
||||
//! 原型方法、`self` 面(`self.postMessage`/`self.close`/`self.name`/`self.location`)
|
||||
//! 与 `MessageEvent` / 错误事件构造辅助。
|
||||
//! 原型方法、`self` 面(`self.postMessage`/`self.close`/`self.name`/`self.location`)、
|
||||
//! `importScripts` 与 `MessageEvent` / 错误事件构造辅助。
|
||||
//!
|
||||
//! 关键约定:
|
||||
//! - 函数是 concrete `&mut Vm` 形态(`NativeFn`),不挂 `VmHost` trait(trait 是单
|
||||
@@ -320,6 +320,72 @@ pub(crate) fn self_close(_vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
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());
|
||||
|
||||
@@ -937,6 +937,152 @@ mod tests {
|
||||
let _ = std::fs::remove_file(&path_b);
|
||||
}
|
||||
|
||||
/// 端到端:importScripts 同步执行文件(lib.js 设全局变量,importScripts 后
|
||||
/// 立即可读);worker 脚本内 typeof importScripts 为 "function"、
|
||||
/// self.importScripts === importScripts。
|
||||
#[test]
|
||||
fn worker_e2e_import_scripts() {
|
||||
let mut vm = vm_with_compiler();
|
||||
|
||||
// lib.js:设全局变量。
|
||||
let lib_path = write_worker_script("globalThis.libValue = 7;");
|
||||
// worker 脚本:顶层 importScripts,收到消息后回发读取结果。
|
||||
let worker_script = format!(
|
||||
"importScripts('{lib}'); \
|
||||
self.addEventListener('message', function(e) {{ self.postMessage(\
|
||||
globalThis.libValue + '|' + (typeof importScripts) + '|' + (self.importScripts === importScripts)); }});",
|
||||
lib = lib_path.display()
|
||||
);
|
||||
let worker_path = write_worker_script(&worker_script);
|
||||
// 主脚本:建 Worker、投递消息触发回发。
|
||||
let main_script = format!(
|
||||
"var w = new Worker('{path}'); w.addEventListener('message', function(e) {{ globalThis.got = e.data; }}); w.postMessage('go');",
|
||||
path = worker_path.display()
|
||||
);
|
||||
vm.run(&Arc::new(compile_script(&main_script))).expect("主脚本应运行");
|
||||
|
||||
// 事件循环轮询至 globalThis.got 被设置(带截止,防 flaky)。
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
loop {
|
||||
vm.deliver_worker_messages();
|
||||
vm.cleanup_disconnected_workers();
|
||||
if global_value_opt(&vm, "got").is_some() {
|
||||
break;
|
||||
}
|
||||
if Instant::now() >= deadline {
|
||||
panic!("5 秒内未收到 importScripts 回发");
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
let got = global_value_opt(&vm, "got").expect("got 应被设置");
|
||||
let got_text = vm.lookup_str(got).expect("got 应可读");
|
||||
assert_eq!(
|
||||
got_text,
|
||||
"7|function|true",
|
||||
"lib.js 应同步执行、importScripts 应为 function 且经 self 可达"
|
||||
);
|
||||
|
||||
vm.shutdown_workers();
|
||||
let _ = std::fs::remove_file(&worker_path);
|
||||
let _ = std::fs::remove_file(&lib_path);
|
||||
}
|
||||
|
||||
/// 端到端:importScripts 多 URL 按序执行(a 先于 b)。
|
||||
#[test]
|
||||
fn worker_e2e_import_scripts_multiple_urls_in_order() {
|
||||
let mut vm = vm_with_compiler();
|
||||
|
||||
// a.js 与 b.js 各向 globalThis.order 追加一段。
|
||||
let path_a = write_worker_script("globalThis.order = (globalThis.order || '') + 'a';");
|
||||
let path_b = write_worker_script("globalThis.order = (globalThis.order || '') + 'b';");
|
||||
let worker_script = format!(
|
||||
"importScripts('{a}', '{b}'); \
|
||||
self.addEventListener('message', function(e) {{ self.postMessage(globalThis.order); }});",
|
||||
a = path_a.display(),
|
||||
b = path_b.display()
|
||||
);
|
||||
let worker_path = write_worker_script(&worker_script);
|
||||
let main_script = format!(
|
||||
"var w = new Worker('{path}'); w.addEventListener('message', function(e) {{ globalThis.got = e.data; }}); w.postMessage('go');",
|
||||
path = worker_path.display()
|
||||
);
|
||||
vm.run(&Arc::new(compile_script(&main_script))).expect("主脚本应运行");
|
||||
|
||||
// 事件循环轮询至 globalThis.got 被设置(带截止,防 flaky)。
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
loop {
|
||||
vm.deliver_worker_messages();
|
||||
vm.cleanup_disconnected_workers();
|
||||
if global_value_opt(&vm, "got").is_some() {
|
||||
break;
|
||||
}
|
||||
if Instant::now() >= deadline {
|
||||
panic!("5 秒内未收到 importScripts 回发");
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
let got = global_value_opt(&vm, "got").expect("got 应被设置");
|
||||
let got_text = vm.lookup_str(got).expect("got 应可读");
|
||||
assert_eq!(got_text, "ab", "多 URL 应按实参顺序执行");
|
||||
|
||||
vm.shutdown_workers();
|
||||
let _ = std::fs::remove_file(&worker_path);
|
||||
let _ = std::fs::remove_file(&path_a);
|
||||
let _ = std::fs::remove_file(&path_b);
|
||||
}
|
||||
|
||||
/// 端到端:importScripts 文件不存在抛 TypeError、语法错误抛 SyntaxError、
|
||||
/// 脚本运行期异常原值透传。
|
||||
#[test]
|
||||
fn worker_e2e_import_scripts_errors() {
|
||||
let mut vm = vm_with_compiler();
|
||||
|
||||
// bad.js:语法错误(编译失败);throw.js:顶层抛字符串(运行期异常)。
|
||||
let bad_path = write_worker_script("function { 语法错误");
|
||||
let throw_path = write_worker_script("throw 'boom';");
|
||||
let worker_script = format!(
|
||||
"var r1 = 'none'; try {{ importScripts('{missing}'); }} catch (e) {{ r1 = e.constructor.name; }} \
|
||||
var r2 = 'none'; try {{ importScripts('{bad}'); }} catch (e) {{ r2 = e.constructor.name; }} \
|
||||
var r3 = 'none'; try {{ importScripts('{throw}'); }} catch (e) {{ r3 = String(e); }} \
|
||||
self.addEventListener('message', function(e) {{ self.postMessage(r1 + '|' + r2 + '|' + r3); }});",
|
||||
missing = "/nonexistent/oxide_import_scripts_missing.js",
|
||||
bad = bad_path.display(),
|
||||
throw = throw_path.display()
|
||||
);
|
||||
let worker_path = write_worker_script(&worker_script);
|
||||
let main_script = format!(
|
||||
"var w = new Worker('{path}'); w.addEventListener('message', function(e) {{ globalThis.got = e.data; }}); w.postMessage('go');",
|
||||
path = worker_path.display()
|
||||
);
|
||||
vm.run(&Arc::new(compile_script(&main_script))).expect("主脚本应运行");
|
||||
|
||||
// 事件循环轮询至 globalThis.got 被设置(带截止,防 flaky)。
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
loop {
|
||||
vm.deliver_worker_messages();
|
||||
vm.cleanup_disconnected_workers();
|
||||
if global_value_opt(&vm, "got").is_some() {
|
||||
break;
|
||||
}
|
||||
if Instant::now() >= deadline {
|
||||
panic!("5 秒内未收到 importScripts 回发");
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
let got = global_value_opt(&vm, "got").expect("got 应被设置");
|
||||
let got_text = vm.lookup_str(got).expect("got 应可读");
|
||||
assert_eq!(
|
||||
got_text,
|
||||
"TypeError|SyntaxError|boom",
|
||||
"文件不存在应抛 TypeError、语法错误应抛 SyntaxError、运行期异常应原值透传"
|
||||
);
|
||||
|
||||
vm.shutdown_workers();
|
||||
let _ = std::fs::remove_file(&worker_path);
|
||||
let _ = std::fs::remove_file(&bad_path);
|
||||
let _ = std::fs::remove_file(&throw_path);
|
||||
}
|
||||
|
||||
/// onmessageerror 交付辅助:无自然触发,直接单测交付辅助(handler 被调用、
|
||||
/// 事件 type 为 "messageerror")。不钉触发可达性(当前架构不可达)。
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user