From c9bbc51435f4cece96aeb636c26c25e9bb7f4fe9 Mon Sep 17 00:00:00 2001 From: Andrew DiZenzo Date: Thu, 4 Jun 2026 04:26:19 +0000 Subject: [PATCH] fix(runtime): align Web Storage accessors --- crates/perry-runtime/src/web_storage.rs | 54 ++++++++++++++++--- .../node-suite/globals/storage-globals.ts | 13 +++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/crates/perry-runtime/src/web_storage.rs b/crates/perry-runtime/src/web_storage.rs index 5348fb3b91..b7c29dff6d 100644 --- a/crates/perry-runtime/src/web_storage.rs +++ b/crates/perry-runtime/src/web_storage.rs @@ -30,14 +30,33 @@ pub extern "C" fn storage_constructor_illegal(_closure: *const ClosureHeader) -> throw_error("Illegal constructor") } -extern "C" fn storage_global_getter(_closure: *const ClosureHeader) -> f64 { - f64::from_bits(TAG_UNDEFINED) +extern "C" fn local_storage_global_getter(_closure: *const ClosureHeader) -> f64 { + storage_global_value(StorageKind::Local) +} + +extern "C" fn session_storage_global_getter(_closure: *const ClosureHeader) -> f64 { + storage_global_value(StorageKind::Session) +} + +extern "C" fn storage_length_getter(_closure: *const ClosureHeader) -> f64 { + let Some(kind) = receiver_kind() else { + return throw_type_error("Illegal invocation"); + }; + with_store(kind, |store| store.len() as f64) } extern "C" fn storage_global_setter(_closure: *const ClosureHeader, _value: f64) -> f64 { f64::from_bits(TAG_UNDEFINED) } +fn storage_global_value(kind: StorageKind) -> f64 { + let obj = storage_object(kind); + if obj.is_null() { + return f64::from_bits(TAG_UNDEFINED); + } + crate::value::js_nanbox_pointer(obj as i64) +} + extern "C" fn storage_get_item(_closure: *const ClosureHeader, key: f64) -> f64 { let Some(kind) = receiver_kind() else { return throw_type_error("Illegal invocation"); @@ -210,8 +229,18 @@ pub(crate) fn install_storage_globals( Ordering::Release, ); - set_global_storage_property(global, "localStorage", local); - set_global_storage_property(global, "sessionStorage", session); + set_global_storage_property( + global, + "localStorage", + local, + local_storage_global_getter as *const u8, + ); + set_global_storage_property( + global, + "sessionStorage", + session, + session_storage_global_getter as *const u8, + ); } fn install_method(proto: *mut ObjectHeader, name: &str, func_ptr: *const u8, arity: u32) { @@ -245,11 +274,11 @@ fn install_method(proto: *mut ObjectHeader, name: &str, func_ptr: *const u8, ari } fn install_storage_length_accessor(proto: *mut ObjectHeader) { - let getter = crate::closure::js_closure_alloc(storage_global_getter as *const u8, 0); + let getter = crate::closure::js_closure_alloc(storage_length_getter as *const u8, 0); if getter.is_null() { return; } - crate::closure::js_register_closure_arity(storage_global_getter as *const u8, 0); + crate::closure::js_register_closure_arity(storage_length_getter as *const u8, 0); crate::object::set_bound_native_closure_name(getter, "get length"); crate::object::js_object_set_field_by_name( proto, @@ -313,7 +342,12 @@ fn make_storage_object(kind: StorageKind, proto: *mut ObjectHeader) -> *mut Obje obj } -fn set_global_storage_property(global: *mut ObjectHeader, name: &str, value: *mut ObjectHeader) { +fn set_global_storage_property( + global: *mut ObjectHeader, + name: &str, + value: *mut ObjectHeader, + getter_ptr: *const u8, +) { crate::object::js_object_set_field_by_name( global, string(name), @@ -325,9 +359,13 @@ fn set_global_storage_property(global: *mut ObjectHeader, name: &str, value: *mu PropertyAttrs::new(true, true, true), ); - let getter = crate::closure::js_closure_alloc(storage_global_getter as *const u8, 0); + let getter = crate::closure::js_closure_alloc(getter_ptr, 0); let setter = crate::closure::js_closure_alloc(storage_global_setter as *const u8, 0); if !getter.is_null() && !setter.is_null() { + crate::closure::js_register_closure_arity(getter_ptr, 0); + crate::closure::js_register_closure_arity(storage_global_setter as *const u8, 1); + crate::object::set_bound_native_closure_name(getter, &format!("get {name}")); + crate::object::set_bound_native_closure_name(setter, &format!("set {name}")); crate::object::set_builtin_accessor_descriptor( global as usize, name.to_string(), diff --git a/test-parity/node-suite/globals/storage-globals.ts b/test-parity/node-suite/globals/storage-globals.ts index dfd88383df..79f9214376 100644 --- a/test-parity/node-suite/globals/storage-globals.ts +++ b/test-parity/node-suite/globals/storage-globals.ts @@ -42,6 +42,19 @@ globalDesc("Storage desc", "Storage"); globalDesc("localStorage desc", "localStorage"); globalDesc("sessionStorage desc", "sessionStorage"); +const localStorageDesc: any = Object.getOwnPropertyDescriptor(globalThis, "localStorage"); +const sessionStorageDesc: any = Object.getOwnPropertyDescriptor(globalThis, "sessionStorage"); +const lengthDesc: any = Object.getOwnPropertyDescriptor(Storage.prototype, "length"); +show( + "storage getter identity", + [ + localStorageDesc.get.call(g) === localStorage, + sessionStorageDesc.get.call(g) === sessionStorage, + ].join("/"), +); +show("length getter session", lengthDesc.get.call(sessionStorage)); +showError("length getter proto", () => lengthDesc.get.call(Storage.prototype)); + for (const name of ["clear", "getItem", "key", "removeItem", "setItem"]) { const fn = (Storage.prototype as any)[name]; const desc: any = Object.getOwnPropertyDescriptor(Storage.prototype, name);