use boa_gc::{Finalize, Trace, WeakGc};
use boa_profiler::Profiler;
use crate::{
builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::Attribute,
realm::Realm,
symbol::JsSymbol,
Context, JsArgs, JsNativeError, JsResult, JsValue,
};
#[derive(Debug, Clone, Trace, Finalize)]
pub(crate) struct WeakRef;
impl IntrinsicObject for WeakRef {
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(),
"WeakRef",
Attribute::CONFIGURABLE,
)
.method(Self::deref, "deref", 0)
.build();
}
}
impl BuiltInObject for WeakRef {
const NAME: &'static str = "WeakRef";
const ATTRIBUTE: Attribute = Attribute::WRITABLE.union(Attribute::CONFIGURABLE);
}
impl BuiltInConstructor for WeakRef {
const LENGTH: usize = 1;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::weak_ref;
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
if new_target.is_undefined() {
return Err(JsNativeError::typ()
.with_message("WeakRef: cannot call constructor without `new`")
.into());
}
let target = args.get(0).and_then(JsValue::as_object).ok_or_else(|| {
JsNativeError::typ().with_message(format!(
"WeakRef: expected target argument of type `object`, got target of type `{}`",
args.get_or_undefined(0).type_of()
))
})?;
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::weak_ref, context)?;
let weak_ref = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::weak_ref(WeakGc::new(target.inner())),
);
context.kept_alive.push(target.clone());
Ok(weak_ref.into())
}
}
impl WeakRef {
pub(crate) fn deref(
this: &JsValue,
_: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let weak_ref = this.as_object().map(JsObject::borrow).ok_or_else(|| {
JsNativeError::typ().with_message(format!(
"WeakRef.prototype.deref: expected `this` to be an `object`, got value of type `{}`",
this.type_of()
))
})?;
let weak_ref = weak_ref.as_weak_ref().ok_or_else(|| {
JsNativeError::typ()
.with_message("WeakRef.prototype.deref: expected `this` to be a `WeakRef` object")
})?;
if let Some(object) = weak_ref.upgrade() {
let object = JsObject::from(object);
context.kept_alive.push(object.clone());
Ok(object.into())
} else {
Ok(JsValue::undefined())
}
}
}
#[cfg(test)]
mod tests {
use indoc::indoc;
use crate::{run_test_actions, JsValue, TestAction};
#[test]
fn weak_ref_collected() {
run_test_actions([
TestAction::assert_with_op(
indoc! {r#"
var ptr;
{
let obj = {a: 5, b: 6};
ptr = new WeakRef(obj);
}
ptr.deref()
"#},
|v, _| v.is_object(),
),
TestAction::inspect_context(|context| {
context.clear_kept_objects();
boa_gc::force_collect();
}),
TestAction::assert_eq("ptr.deref()", JsValue::undefined()),
]);
}
}