pub struct JavascriptObjects {
heap: JavascriptHeap,
properties: PropertyStore<ManagedId, JavascriptPropertyKey, JavascriptValue, Accessor>,
prototypes: BTreeMap<ManagedId, ManagedHandle>,
functions: BTreeMap<ManagedId, JavascriptFunction>,
lexical_this: BTreeMap<ManagedId, JavascriptValue>,
}
impl JavascriptObjects {
pub fn new(heap: JavascriptHeap) -> Self {
Self {
heap,
properties: PropertyStore::new(),
prototypes: BTreeMap::new(),
functions: BTreeMap::new(),
lexical_this: BTreeMap::new(),
}
}
pub fn ordinary(&mut self) -> Result<ManagedHandle, JavascriptObjectError> {
Ok(self
.heap
.allocate(JavascriptManagedObject::new(JavascriptManagedKind::Object))?)
}
pub fn function(
&mut self,
function: JavascriptFunction,
lexical_this: Option<JavascriptValue>,
) -> Result<ManagedHandle, JavascriptObjectError> {
let h = self.heap.allocate(JavascriptManagedObject::new(
JavascriptManagedKind::Function,
))?;
for capture in function.captures() {
self.heap.connect(h, capture.managed())?;
}
if let Some(v) = lexical_this {
self.lexical_this.insert(h.id(), v);
}
self.functions.insert(h.id(), function);
Ok(h)
}
pub fn call_this(
&self,
function: ManagedHandle,
receiver: JavascriptValue,
) -> Result<JavascriptThis, JavascriptObjectError> {
let f = self
.functions
.get(&function.id())
.ok_or(JavascriptObjectError::NotConstructor)?;
Ok(if f.policy.kind == JavascriptFunctionKind::Arrow {
JavascriptThis::Lexical(
self.lexical_this
.get(&function.id())
.cloned()
.unwrap_or(JavascriptValue::Undefined),
)
} else {
JavascriptThis::Dynamic(receiver)
})
}
pub fn call<T, E>(
&self,
function: ManagedHandle,
receiver: JavascriptValue,
arguments: &[JavascriptValue],
body: impl FnOnce(
&FunctionPlan,
&[CapturedBinding],
JavascriptThis,
&[JavascriptValue],
) -> Result<T, E>,
) -> Result<T, E>
where
E: From<JavascriptObjectError>,
{
let metadata = self
.functions
.get(&function.id())
.ok_or(JavascriptObjectError::NotConstructor)?;
let this = self.call_this(function, receiver)?;
body(metadata.plan(), metadata.captures(), this, arguments)
}
pub fn construct(
&mut self,
function: ManagedHandle,
) -> Result<ManagedHandle, JavascriptObjectError> {
let f = self
.functions
.get(&function.id())
.ok_or(JavascriptObjectError::NotConstructor)?;
if !f.policy.constructable || f.policy.kind == JavascriptFunctionKind::Arrow {
return Err(JavascriptObjectError::NotConstructor);
}
let instance = self.ordinary()?;
self.set_prototype(instance, function)?;
Ok(instance)
}
pub fn set_prototype(
&mut self,
object: ManagedHandle,
prototype: ManagedHandle,
) -> Result<(), JavascriptObjectError> {
let mut at = Some(prototype);
let mut seen = HashSet::new();
while let Some(h) = at {
if h.id() == object.id() || !seen.insert(h.id()) {
return Err(JavascriptObjectError::PrototypeCycle);
}
at = self.prototypes.get(&h.id()).copied();
}
self.heap.connect(object, prototype)?;
self.prototypes.insert(object.id(), prototype);
Ok(())
}
pub fn define_data(
&mut self,
object: ManagedHandle,
key: JavascriptPropertyKey,
value: JavascriptValue,
writable: bool,
enumerable: bool,
configurable: bool,
) -> Result<(), JavascriptObjectError> {
self.properties.define(
&object.id(),
key,
Descriptor::Data(DataDescriptor {
value,
writable,
enumerable,
configurable,
}),
)?;
Ok(())
}
pub fn define_accessor(
&mut self,
object: ManagedHandle,
key: JavascriptPropertyKey,
get: Option<JavascriptValue>,
set: bool,
enumerable: bool,
configurable: bool,
) -> Result<(), JavascriptObjectError> {
self.properties.define(
&object.id(),
key,
Descriptor::Accessor(AccessorDescriptor {
get: Some(Accessor { get, set }),
set: Some(Accessor { get: None, set }),
enumerable,
configurable,
}),
)?;
Ok(())
}
fn chain(
&self,
object: ManagedHandle,
budget: usize,
) -> Result<Vec<ManagedId>, JavascriptObjectError> {
let mut out = Vec::new();
let mut at = Some(object);
let mut seen = HashSet::new();
while let Some(h) = at {
if out.len() >= budget {
return Err(JavascriptObjectError::Access);
}
if !seen.insert(h.id()) {
break;
}
out.push(h.id());
at = self.prototypes.get(&h.id()).copied();
}
Ok(out)
}
pub fn get(
&self,
object: ManagedHandle,
key: &JavascriptPropertyKey,
budget: usize,
) -> Result<Option<JavascriptValue>, JavascriptObjectError> {
let chain = self.chain(object, budget)?;
let mut hooks = Hooks::default();
self.properties
.get(
&chain,
&object.id(),
key,
&mut AccessContext::new(budget),
&mut hooks,
)
.map_err(|_| JavascriptObjectError::Access)
}
pub fn set(
&mut self,
object: ManagedHandle,
key: &JavascriptPropertyKey,
value: JavascriptValue,
budget: usize,
) -> Result<bool, JavascriptObjectError> {
let chain = self.chain(object, budget)?;
let mut hooks = Hooks::default();
self.properties
.set(
&chain,
&object.id(),
key,
value,
&mut AccessContext::new(budget),
&mut hooks,
)
.map_err(|_| JavascriptObjectError::Access)
}
pub fn delete(
&mut self,
object: ManagedHandle,
key: &JavascriptPropertyKey,
) -> Result<bool, JavascriptObjectError> {
Ok(self.properties.delete(&object.id(), key)?)
}
pub fn enumerable_keys(&self, object: ManagedHandle) -> Vec<JavascriptPropertyKey> {
let keys = self.properties.own_keys(&object.id(), true);
let mut indices = Vec::new();
let mut strings = Vec::new();
let mut symbols = Vec::new();
for key in keys {
match &key {
JavascriptPropertyKey::String(s) => match s.parse::<u32>() {
Ok(n) if n != u32::MAX && n.to_string() == *s => indices.push((n, key)),
_ => strings.push(key),
},
JavascriptPropertyKey::Symbol(_) => symbols.push(key),
JavascriptPropertyKey::Private { .. } => {}
}
}
indices.sort_by_key(|(n, _)| *n);
indices
.into_iter()
.map(|(_, k)| k)
.chain(strings)
.chain(symbols)
.collect()
}
pub fn private_key(
&self,
class: ManagedHandle,
name: &str,
) -> Result<JavascriptPropertyKey, JavascriptObjectError> {
let f = self
.functions
.get(&class.id())
.ok_or(JavascriptObjectError::PrivateBrand)?;
if !f.private_names.iter().any(|n| n == name) {
return Err(JavascriptObjectError::PrivateBrand);
}
Ok(JavascriptPropertyKey::Private {
class: class.id(),
name: name.into(),
})
}
pub fn collect(
&mut self,
) -> Result<Option<sim_lib_gc_tracing::CollectionReceipt>, sim_lib_gc_tracing::CollectionError>
{
self.heap.collect()
}
pub fn live_len(&self) -> usize {
self.heap.live_len()
}
}
pub const fn javascript_callable_shape_constraints() -> &'static [&'static str] {
&[]
}
pub const fn javascript_object_gaps() -> &'static [JavascriptObjectGap] {
&[
JavascriptObjectGap::ProxyInvariants,
JavascriptObjectGap::ExoticObjectInvariants,
]
}