use std::collections::BTreeMap;
use boa_engine::object::ObjectInitializer;
use boa_engine::property::Attribute;
use boa_engine::{Context, JsArgs, JsResult, JsValue};
use super::bind::{host, param_error, qualified};
const NOT_FOUND: &str = "Global value not found.";
#[derive(Debug, Clone)]
pub(crate) struct Entry {
pub(crate) value: Option<JsValue>,
pub(crate) persistent: bool,
}
pub(crate) type Bag = BTreeMap<String, Entry>;
fn set_persistent(_t: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if args.len() != 2 {
return Err(param_error("global.setPersistent"));
}
let name = args
.get_or_undefined(0)
.clone()
.to_string(context)?
.to_std_string_lossy();
let persist = args.get_or_undefined(1).to_boolean();
let Some(host) = host(context) else {
return Err(qualified("global.setPersistent", NOT_FOUND));
};
let mut state = host.borrow_mut();
let Some(entry) = state.globals.get_mut(&name) else {
return Err(qualified("global.setPersistent", NOT_FOUND));
};
if entry.value.is_none() {
return Err(qualified("global.setPersistent", NOT_FOUND));
}
entry.persistent = persist;
Ok(JsValue::undefined())
}
fn trap_get(_t: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let key = args.get_or_undefined(1).clone();
let name = key.clone().to_string(context)?.to_std_string_lossy();
if let Some(entry) = host(context)
.and_then(|host| host.borrow().globals.get(&name).cloned())
.and_then(|entry| entry.value)
{
return Ok(entry);
}
let Some(target) = args.get_or_undefined(0).as_object() else {
return Ok(JsValue::undefined());
};
target.get(key.to_property_key(context)?, context)
}
fn trap_set(_t: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let name = args
.get_or_undefined(1)
.clone()
.to_string(context)?
.to_std_string_lossy();
let value = args.get_or_undefined(2).clone();
if let Some(host) = host(context) {
let mut state = host.borrow_mut();
if value.is_undefined() {
if let Some(entry) = state.globals.get_mut(&name) {
entry.value = None;
}
} else {
state.globals.insert(
name,
Entry {
value: Some(value),
persistent: false,
},
);
}
}
Ok(JsValue::from(true))
}
fn trap_delete(_t: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let name = args
.get_or_undefined(1)
.clone()
.to_string(context)?
.to_std_string_lossy();
if let Some(host) = host(context)
&& let Some(entry) = host.borrow_mut().globals.get_mut(&name)
{
entry.value = None;
}
Ok(JsValue::from(true))
}
fn trap_has(_t: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let name = args
.get_or_undefined(1)
.clone()
.to_string(context)?
.to_std_string_lossy();
if name == "setPersistent" {
return Ok(JsValue::from(true));
}
Ok(JsValue::from(host(context).is_some_and(|host| {
host.borrow()
.globals
.get(&name)
.is_some_and(|entry| entry.value.is_some())
})))
}
#[allow(
clippy::unnecessary_wraps,
reason = "the bound-function signature every trap shares"
)]
fn trap_own_keys(_t: &JsValue, _a: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let mut keys = vec![JsValue::from(boa_engine::js_string!("setPersistent"))];
if let Some(host) = host(context) {
for (name, entry) in &host.borrow().globals {
if entry.value.is_some() {
keys.push(JsValue::from(boa_engine::js_string!(name.clone())));
}
}
}
Ok(JsValue::from(
boa_engine::object::builtins::JsArray::from_iter(keys, context),
))
}
fn trap_descriptor(_t: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let name = args
.get_or_undefined(1)
.clone()
.to_string(context)?
.to_std_string_lossy();
let value = if name == "setPersistent" {
args.get_or_undefined(0)
.as_object()
.map(|target| target.get(boa_engine::js_string!("setPersistent"), context))
.transpose()?
} else {
host(context).and_then(|host| {
host.borrow()
.globals
.get(&name)
.and_then(|entry| entry.value.clone())
})
};
let Some(value) = value else {
return Ok(JsValue::undefined());
};
let descriptor = ObjectInitializer::new(context)
.property(boa_engine::js_string!("value"), value, Attribute::all())
.property(
boa_engine::js_string!("writable"),
JsValue::from(true),
Attribute::all(),
)
.property(
boa_engine::js_string!("enumerable"),
JsValue::from(true),
Attribute::all(),
)
.property(
boa_engine::js_string!("configurable"),
JsValue::from(true),
Attribute::all(),
)
.build();
Ok(JsValue::from(descriptor))
}
pub(crate) fn install(context: &mut Context) -> JsResult<()> {
let target = ObjectInitializer::new(context)
.function(
super::bind::native(set_persistent),
boa_engine::js_string!("setPersistent"),
2,
)
.build();
let proxy = boa_engine::object::builtins::JsProxy::builder(target)
.get(trap_get)
.set(trap_set)
.delete_property(trap_delete)
.has(trap_has)
.own_keys(trap_own_keys)
.get_own_property_descriptor(trap_descriptor)
.build(context)?;
context.register_global_property(
boa_engine::js_string!("global"),
JsValue::from(proxy),
Attribute::all(),
)?;
Ok(())
}