mirror of https://github.com/boa-dev/boa.git
Browse Source
52/60 tests passing. The remaining tests are either features not implemented ([FinalizationRegistry](https://tc39.es/ecma262/multipage/managing-memory.html#sec-finalization-registry-objects)) or features still in development ([symbols-as-weakmap-keys](https://github.com/tc39/proposal-symbols-as-weakmap-keys)).pull/2441/head
José Julián Espina
2 years ago
16 changed files with 264 additions and 38 deletions
@ -0,0 +1,3 @@
|
||||
mod weak_ref; |
||||
|
||||
pub(crate) use weak_ref::WeakRef; |
@ -0,0 +1,166 @@
|
||||
use boa_gc::{Finalize, Trace, WeakGc}; |
||||
use boa_profiler::Profiler; |
||||
use tap::{Conv, Pipe}; |
||||
|
||||
use crate::{ |
||||
builtins::{BuiltIn, JsArgs}, |
||||
context::intrinsics::StandardConstructors, |
||||
object::{ |
||||
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, |
||||
}, |
||||
property::Attribute, |
||||
symbol::WellKnownSymbols, |
||||
Context, JsNativeError, JsResult, JsValue, |
||||
}; |
||||
|
||||
/// The [`WeakRef`][wr] builtin object.
|
||||
///
|
||||
/// [wr]: https://tc39.es/ecma262/#sec-weak-ref-objects
|
||||
#[derive(Debug, Clone, Trace, Finalize)] |
||||
pub(crate) struct WeakRef; |
||||
|
||||
impl BuiltIn for WeakRef { |
||||
const NAME: &'static str = "WeakRef"; |
||||
|
||||
const ATTRIBUTE: Attribute = Attribute::WRITABLE.union(Attribute::CONFIGURABLE); |
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> { |
||||
let _timer = Profiler::global().start_event(Self::NAME, "init"); |
||||
ConstructorBuilder::with_standard_constructor( |
||||
context, |
||||
WeakRef::constructor, |
||||
context.intrinsics().constructors().weak_ref().clone(), |
||||
) |
||||
.name(Self::NAME) |
||||
.length(Self::LENGTH) |
||||
.property( |
||||
WellKnownSymbols::to_string_tag(), |
||||
"WeakRef", |
||||
Attribute::CONFIGURABLE, |
||||
) |
||||
.method(WeakRef::deref, "deref", 0) |
||||
.build() |
||||
.conv::<JsValue>() |
||||
.pipe(Some) |
||||
} |
||||
} |
||||
|
||||
impl WeakRef { |
||||
/// The amount of arguments the `WeakRef` constructor takes.
|
||||
pub(crate) const LENGTH: usize = 1; |
||||
|
||||
/// Constructor [`WeakRef ( target )`][cons]
|
||||
///
|
||||
/// [cons]: https://tc39.es/ecma262/#sec-weak-ref-target
|
||||
pub(crate) fn constructor( |
||||
new_target: &JsValue, |
||||
args: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// If NewTarget is undefined, throw a TypeError exception.
|
||||
if new_target.is_undefined() { |
||||
return Err(JsNativeError::typ() |
||||
.with_message("WeakRef: cannot call constructor without `new`") |
||||
.into()); |
||||
} |
||||
|
||||
// 2. If target is not an Object, throw a TypeError exception.
|
||||
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() |
||||
)) |
||||
})?; |
||||
|
||||
// 3. Let weakRef be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakRef.prototype%", « [[WeakRefTarget]] »).
|
||||
// 5. Set weakRef.[[WeakRefTarget]] to target.
|
||||
let weak_ref = JsObject::from_proto_and_data( |
||||
get_prototype_from_constructor(new_target, StandardConstructors::weak_ref, context)?, |
||||
ObjectData::weak_ref(WeakGc::new(target.inner())), |
||||
); |
||||
|
||||
// 4. Perform AddToKeptObjects(target).
|
||||
context.kept_alive.push(target.clone()); |
||||
|
||||
// 6. Return weakRef.
|
||||
Ok(weak_ref.into()) |
||||
} |
||||
|
||||
/// Method [`WeakRef.prototype.deref ( )`][spec].
|
||||
///
|
||||
/// If the referenced object hasn't been collected, this method promotes a `WeakRef` into a
|
||||
/// proper [`JsObject`], or returns `undefined` otherwise.
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-weak-ref.prototype.deref
|
||||
pub(crate) fn deref(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { |
||||
// 1. Let weakRef be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(weakRef, [[WeakRefTarget]]).
|
||||
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") |
||||
})?; |
||||
|
||||
// 3. Return WeakRefDeref(weakRef).
|
||||
|
||||
// `WeakRefDeref`
|
||||
// https://tc39.es/ecma262/multipage/managing-memory.html#sec-weakrefderef
|
||||
// 1. Let target be weakRef.[[WeakRefTarget]].
|
||||
// 2. If target is not empty, then
|
||||
if let Some(object) = weak_ref.upgrade() { |
||||
let object = JsObject::from(object); |
||||
|
||||
// a. Perform AddToKeptObjects(target).
|
||||
context.kept_alive.push(object.clone()); |
||||
|
||||
// b. Return target.
|
||||
Ok(object.into()) |
||||
} else { |
||||
// 3. Return undefined.
|
||||
Ok(JsValue::undefined()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use crate::{Context, JsValue}; |
||||
|
||||
#[test] |
||||
fn weak_ref_collected() { |
||||
let context = &mut Context::default(); |
||||
|
||||
assert!(context |
||||
.eval( |
||||
r#" |
||||
var ptr; |
||||
{ |
||||
let obj = {a: 5, b: 6}; |
||||
ptr = new WeakRef(obj); |
||||
} |
||||
ptr.deref() |
||||
"# |
||||
) |
||||
.unwrap() |
||||
.is_object()); |
||||
|
||||
boa_gc::force_collect(); |
||||
|
||||
assert_eq!( |
||||
context |
||||
.eval( |
||||
r#" |
||||
ptr.deref() |
||||
"# |
||||
) |
||||
.unwrap(), |
||||
JsValue::undefined() |
||||
) |
||||
} |
||||
} |
Loading…
Reference in new issue