use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use serde_json::Value;
use super::element_handle::ElementHandle;
use super::frame::ExecutionContext;
pub struct JSHandle {
execution_context: Rc<ExecutionContext>,
remote_object_id: String,
json_value_cache: RefCell<Option<Value>>,
properties: RefCell<HashMap<String, Rc<JSHandle>>>,
disposed: RefCell<bool>,
is_element: bool,
}
impl std::fmt::Debug for JSHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JSHandle")
.field("remote_object_id", &self.remote_object_id)
.field("is_element", &self.is_element)
.field("disposed", &self.disposed.borrow())
.finish()
}
}
impl JSHandle {
pub fn new(
execution_context: Rc<ExecutionContext>,
remote_object_id: impl Into<String>,
) -> Self {
Self {
execution_context,
remote_object_id: remote_object_id.into(),
json_value_cache: RefCell::new(None),
properties: RefCell::new(HashMap::new()),
disposed: RefCell::new(false),
is_element: false,
}
}
pub fn remote_object_id(&self) -> &str {
&self.remote_object_id
}
pub fn execution_context(&self) -> &ExecutionContext {
&self.execution_context
}
pub fn is_disposed(&self) -> bool {
*self.disposed.borrow()
}
pub fn as_element(&self) -> Option<&ElementHandle> {
None
}
pub fn json_value(&self) -> Option<Value> {
self.json_value_cache.borrow().clone()
}
pub fn set_json_value(&self, value: Value) {
*self.json_value_cache.borrow_mut() = Some(value);
}
pub fn get_property(&self, name: &str) -> Option<Rc<JSHandle>> {
self.properties.borrow().get(name).cloned()
}
pub fn get_properties(&self) -> Vec<(String, Rc<JSHandle>)> {
self.properties
.borrow()
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
pub fn set_property(&self, name: impl Into<String>, handle: Rc<JSHandle>) {
self.properties.borrow_mut().insert(name.into(), handle);
}
pub fn dispose(&self) {
*self.disposed.borrow_mut() = true;
self.properties.borrow_mut().clear();
*self.json_value_cache.borrow_mut() = None;
}
pub fn is_element_handle(&self) -> bool {
self.is_element
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_ctx() -> Rc<ExecutionContext> {
Rc::new(ExecutionContext::new("CTX-1".to_string()))
}
#[test]
fn new_initial_state() {
let h = JSHandle::new(make_ctx(), "OBJ-1");
assert_eq!(h.remote_object_id(), "OBJ-1");
assert!(!h.is_disposed());
assert!(!h.is_element_handle());
assert!(h.json_value().is_none());
assert!(h.get_properties().is_empty());
}
#[test]
fn as_element_returns_none_for_jshandle() {
let h = JSHandle::new(make_ctx(), "OBJ-1");
assert!(h.as_element().is_none());
}
#[test]
fn set_json_value_then_get() {
let h = JSHandle::new(make_ctx(), "OBJ-1");
assert!(h.json_value().is_none());
h.set_json_value(Value::from(42));
assert_eq!(h.json_value(), Some(Value::from(42)));
}
#[test]
fn set_property_then_get() {
let h = JSHandle::new(make_ctx(), "OBJ-1");
let child = Rc::new(JSHandle::new(make_ctx(), "OBJ-2"));
h.set_property("foo", child.clone());
assert_eq!(h.get_properties().len(), 1);
let got = h.get_property("foo").unwrap();
assert_eq!(got.remote_object_id(), "OBJ-2");
}
#[test]
fn dispose_clears_state() {
let h = JSHandle::new(make_ctx(), "OBJ-1");
let child = Rc::new(JSHandle::new(make_ctx(), "OBJ-2"));
h.set_property("foo", child);
h.set_json_value(Value::from(1));
h.dispose();
assert!(h.is_disposed());
assert!(h.json_value().is_none());
assert!(h.get_properties().is_empty());
assert!(h.get_property("foo").is_none());
}
#[test]
fn execution_context_returns_ref() {
let ctx = make_ctx();
let h = JSHandle::new(ctx.clone(), "OBJ-1");
assert_eq!(h.execution_context().id(), "CTX-1");
}
}