use crate::{
builtins::{
map::add_entries_from_iterable, BuiltInBuilder, BuiltInConstructor, BuiltInObject,
IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::Attribute,
realm::Realm,
string::utf16,
symbol::JsSymbol,
Context, JsArgs, JsNativeError, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
#[derive(Debug, Trace, Finalize)]
pub(crate) struct WeakMap;
impl IntrinsicObject for WeakMap {
fn get(intrinsics: &Intrinsics) -> JsObject {
Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
}
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.property(
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.method(Self::delete, "delete", 1)
.method(Self::get, "get", 1)
.method(Self::has, "has", 1)
.method(Self::set, "set", 2)
.build();
}
}
impl BuiltInObject for WeakMap {
const NAME: &'static str = "WeakMap";
const ATTRIBUTE: Attribute = Attribute::WRITABLE.union(Attribute::CONFIGURABLE);
}
impl BuiltInConstructor for WeakMap {
const LENGTH: usize = 0;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::weak_map;
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
if new_target.is_undefined() {
return Err(JsNativeError::typ()
.with_message("WeakMap: cannot call constructor without `new`")
.into());
}
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::weak_map, context)?;
let map = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::weak_map(boa_gc::WeakMap::new()),
);
let iterable = args.get_or_undefined(0);
if iterable.is_null_or_undefined() {
return Ok(map.into());
}
let adder = map.get(utf16!("set"), context)?;
if !adder.is_callable() {
return Err(JsNativeError::typ()
.with_message("WeakMap: 'add' is not a function")
.into());
}
add_entries_from_iterable(&map, iterable, &adder, context)
}
}
impl WeakMap {
pub(crate) fn delete(
this: &JsValue,
args: &[JsValue],
_context: &mut Context<'_>,
) -> JsResult<JsValue> {
let Some(obj) = this.as_object() else {
return Err(JsNativeError::typ()
.with_message("WeakMap.delete: called with non-object value")
.into());
};
let mut obj_borrow = obj.borrow_mut();
let m = obj_borrow.as_weak_map_mut().ok_or_else(|| {
JsNativeError::typ().with_message("WeakMap.delete: called with non-object value")
})?;
let Some(key) = args.get_or_undefined(0).as_object() else {
return Ok(false.into());
};
Ok(m.remove(key.inner()).is_some().into())
}
pub(crate) fn get(
this: &JsValue,
args: &[JsValue],
_context: &mut Context<'_>,
) -> JsResult<JsValue> {
let Some(obj) = this.as_object() else {
return Err(JsNativeError::typ()
.with_message("WeakMap.get: called with non-object value")
.into());
};
let mut obj_borrow = obj.borrow_mut();
let m = obj_borrow.as_weak_map_mut().ok_or_else(|| {
JsNativeError::typ().with_message("WeakMap.get: called with non-object value")
})?;
let Some(key) = args.get_or_undefined(0).as_object() else {
return Ok(JsValue::undefined());
};
Ok(m.get(key.inner()).unwrap_or_default())
}
pub(crate) fn has(
this: &JsValue,
args: &[JsValue],
_context: &mut Context<'_>,
) -> JsResult<JsValue> {
let Some(obj) = this.as_object() else {
return Err(JsNativeError::typ()
.with_message("WeakMap.has: called with non-object value")
.into());
};
let mut obj_borrow = obj.borrow_mut();
let m = obj_borrow.as_weak_map_mut().ok_or_else(|| {
JsNativeError::typ().with_message("WeakMap.has: called with non-object value")
})?;
let Some(key) = args.get_or_undefined(0).as_object() else {
return Ok(false.into());
};
Ok(m.contains_key(key.inner()).into())
}
pub(crate) fn set(
this: &JsValue,
args: &[JsValue],
_context: &mut Context<'_>,
) -> JsResult<JsValue> {
let Some(obj) = this.as_object() else {
return Err(JsNativeError::typ()
.with_message("WeakMap.set: called with non-object value")
.into());
};
let mut obj_borrow = obj.borrow_mut();
let m = obj_borrow.as_weak_map_mut().ok_or_else(|| {
JsNativeError::typ().with_message("WeakMap.set: called with non-object value")
})?;
let key = args.get_or_undefined(0);
let Some(key) = key.as_object() else {
return Err(JsNativeError::typ()
.with_message(format!(
"WeakMap.set: expected target argument of type `object`, got target of type `{}`",
key.type_of()
)).into());
};
m.insert(key.inner(), args.get_or_undefined(1).clone());
Ok(this.clone())
}
}