use crate::{
ecmascript::{
Agent, ArgumentsList, ExceptionType, FinalizationRegistry, JsError, JsResult, Object,
Value, WeakKey, call_function, key_for_symbol,
},
engine::{Bindable, GcScope, Global, NoGcScope, Scopable, ScopableCollection},
};
pub(crate) fn clear_kept_objects(agent: &mut Agent) {
if agent.kept_alive {
agent.kept_alive = false;
for weak_ref_data in agent.heap.weak_refs.iter_mut() {
weak_ref_data.clear_kept_objects();
}
}
}
pub(crate) fn add_to_kept_objects(agent: &mut Agent, _value: WeakKey) {
agent.kept_alive = true;
}
pub(crate) struct FinalizationRegistryCleanupJob {
finalization_registry: Global<FinalizationRegistry<'static>>,
}
impl FinalizationRegistryCleanupJob {
pub(crate) fn new(agent: &mut Agent, finalization_registry: FinalizationRegistry) -> Self {
Self {
finalization_registry: Global::new(agent, finalization_registry.unbind()),
}
}
pub(crate) fn run<'gc>(self, agent: &mut Agent, gc: GcScope<'gc, '_>) {
let finalization_registry = self.finalization_registry.take(agent).bind(gc.nogc());
let cleanup_result =
cleanup_finalization_registry(agent, finalization_registry.unbind(), gc);
if cleanup_result.is_err() {
let _ = cleanup_result;
}
}
}
pub(crate) fn cleanup_finalization_registry<'gc>(
agent: &mut Agent,
finalization_registry: FinalizationRegistry,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, ()> {
let finalization_registry = finalization_registry.bind(gc.nogc());
let (callback, mut queue) = finalization_registry.get_cleanup_queue(agent);
if queue.is_empty() {
return Ok(());
}
let value = queue.pop().unwrap();
if queue.len() == 1 {
let _ = call_function(
agent,
callback.unbind(),
Value::Undefined,
Some(ArgumentsList::from_mut_value(&mut value.unbind())),
gc,
)?;
} else {
let scoped_callback = callback.scope(agent, gc.nogc());
let finalization_registry = finalization_registry.scope(agent, gc.nogc());
let queue = queue.scope(agent, gc.nogc());
let result = call_function(
agent,
callback.unbind(),
Value::Undefined,
Some(ArgumentsList::from_mut_value(&mut value.unbind())),
gc.reborrow(),
)
.unbind()
.bind(gc.nogc());
let mut err_and_i = None;
if let Err(err) = result {
err_and_i = Some((err.unbind(), 0));
} else {
for (i, value) in queue.iter(agent).enumerate() {
let value = value.get(gc.nogc());
let result = call_function(
agent,
scoped_callback.get(agent),
Value::Undefined,
Some(ArgumentsList::from_mut_value(&mut value.unbind())),
gc.reborrow(),
)
.unbind()
.bind(gc.nogc());
if let Err(err) = result {
err_and_i = Some((err.unbind(), i));
break;
}
}
}
if let Some((err, i)) = err_and_i {
let err = err.unbind();
let gc = gc.into_nogc();
let err = err.bind(gc);
let mut queue = queue.take(agent).bind(gc);
queue.drain(0..i);
let finalization_registry = unsafe { finalization_registry.take(agent) }.bind(gc);
let _ = unsafe { scoped_callback.take(agent) };
if !queue.is_empty() {
finalization_registry.add_cleanups(agent, queue);
}
return Err(err);
}
}
Ok(())
}
pub(crate) fn can_be_held_weakly<'a>(agent: &Agent, v: Value<'a>) -> Option<WeakKey<'a>> {
if let Ok(v) = Object::try_from(v) {
Some(v.into())
} else if let Value::Symbol(v) = v {
if key_for_symbol(agent, v).is_some() {
None
} else {
Some(WeakKey::Symbol(v))
}
} else {
None
}
}
pub(crate) fn throw_not_weak_key_error<'a>(
agent: &mut Agent,
target: Value,
gc: NoGcScope<'a, '_>,
) -> JsError<'a> {
let string_repr = target.try_string_repr(agent, gc);
let message = format!(
"{} is not a non-null object or unique symbol",
string_repr.to_string_lossy_(agent)
);
agent.throw_exception(ExceptionType::TypeError, message, gc)
}