use std::cell::RefCell;
use std::rc::Rc;
use super::frame::Frame;
use super::js_handle::JSHandle;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BoundingBox {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
pub struct ElementHandle {
js: Rc<JSHandle>,
owner_frame: Rc<Frame>,
visible: RefCell<Option<bool>>,
enabled: RefCell<Option<bool>>,
checked: RefCell<Option<bool>>,
editable: RefCell<Option<bool>>,
bbox: RefCell<Option<BoundingBox>>,
content_frame: RefCell<Option<Rc<Frame>>>,
scroll_needed: RefCell<bool>,
}
impl std::fmt::Debug for ElementHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ElementHandle")
.field("remote_object_id", &self.js.remote_object_id())
.field("owner_frame", &self.owner_frame.id())
.field("visible", &self.visible.borrow())
.field("bbox", &self.bbox.borrow())
.finish()
}
}
impl ElementHandle {
pub fn new(js: Rc<JSHandle>, owner_frame: Rc<Frame>) -> Self {
Self {
js,
owner_frame,
visible: RefCell::new(None),
enabled: RefCell::new(None),
checked: RefCell::new(None),
editable: RefCell::new(None),
bbox: RefCell::new(None),
content_frame: RefCell::new(None),
scroll_needed: RefCell::new(false),
}
}
pub fn as_js_handle(&self) -> &JSHandle {
&self.js
}
pub fn as_element(&self) -> Option<&ElementHandle> {
Some(self)
}
pub fn owner_frame(&self) -> Rc<Frame> {
self.owner_frame.clone()
}
pub fn is_visible(&self) -> Option<bool> {
*self.visible.borrow()
}
pub fn set_visible(&self, v: bool) {
*self.visible.borrow_mut() = Some(v);
}
pub fn is_hidden(&self) -> Option<bool> {
self.visible.borrow().map(|v| !v)
}
pub fn is_enabled(&self) -> Option<bool> {
*self.enabled.borrow()
}
pub fn set_enabled(&self, e: bool) {
*self.enabled.borrow_mut() = Some(e);
}
pub fn is_disabled(&self) -> Option<bool> {
self.enabled.borrow().map(|e| !e)
}
pub fn is_checked(&self) -> Option<bool> {
*self.checked.borrow()
}
pub fn set_checked(&self, c: bool) {
*self.checked.borrow_mut() = Some(c);
}
pub fn is_editable(&self) -> Option<bool> {
*self.editable.borrow()
}
pub fn set_editable(&self, e: bool) {
*self.editable.borrow_mut() = Some(e);
}
pub fn bounding_box(&self) -> Option<BoundingBox> {
*self.bbox.borrow()
}
pub fn set_bounding_box(&self, b: BoundingBox) {
*self.bbox.borrow_mut() = Some(b);
}
pub fn content_frame(&self) -> Option<Rc<Frame>> {
self.content_frame.borrow().clone()
}
pub fn set_content_frame(&self, f: Rc<Frame>) {
*self.content_frame.borrow_mut() = Some(f);
}
pub fn scroll_into_view_needed(&self) -> bool {
*self.scroll_needed.borrow()
}
pub fn set_scroll_into_view_needed(&self, needed: bool) {
*self.scroll_needed.borrow_mut() = needed;
}
pub fn dispose(&self) {
self.js.dispose();
*self.visible.borrow_mut() = None;
*self.enabled.borrow_mut() = None;
*self.checked.borrow_mut() = None;
*self.editable.borrow_mut() = None;
*self.bbox.borrow_mut() = None;
*self.content_frame.borrow_mut() = None;
*self.scroll_needed.borrow_mut() = false;
}
pub fn is_disposed(&self) -> bool {
self.js.is_disposed()
}
pub fn remote_object_id(&self) -> &str {
self.js.remote_object_id()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::frame::ExecutionContext;
use crate::api::page::Page;
use std::rc::Rc;
fn make_handle() -> (ElementHandle, Rc<Page>) {
let browser = Rc::new(crate::api::browser::Browser::new_for_test("ws://x"));
let ctx = crate::api::browser::Browser::new_context_for_test(&browser);
let page = crate::api::browser_context::BrowserContext::new_page_for_test(&ctx);
let frame = page.main_frame().clone();
let js_handle = Rc::new(JSHandle::new(
Rc::new(ExecutionContext::new("CTX-1".into())),
"OBJ-1",
));
(ElementHandle::new(js_handle, frame), page)
}
#[test]
fn as_element_returns_some() {
let (h, _p) = make_handle();
assert!(h.as_element().is_some());
}
#[test]
fn visibility_cache_round_trip() {
let (h, _p) = make_handle();
assert!(h.is_visible().is_none());
assert!(h.is_hidden().is_none());
h.set_visible(true);
assert_eq!(h.is_visible(), Some(true));
assert_eq!(h.is_hidden(), Some(false));
}
#[test]
fn enabled_cache_round_trip() {
let (h, _p) = make_handle();
assert!(h.is_enabled().is_none());
h.set_enabled(false);
assert_eq!(h.is_enabled(), Some(false));
assert_eq!(h.is_disabled(), Some(true));
}
#[test]
fn checked_cache_round_trip() {
let (h, _p) = make_handle();
assert!(h.is_checked().is_none());
h.set_checked(true);
assert_eq!(h.is_checked(), Some(true));
}
#[test]
fn editable_cache_round_trip() {
let (h, _p) = make_handle();
assert!(h.is_editable().is_none());
h.set_editable(true);
assert_eq!(h.is_editable(), Some(true));
}
#[test]
fn bounding_box_cache() {
let (h, _p) = make_handle();
assert!(h.bounding_box().is_none());
h.set_bounding_box(BoundingBox {
x: 10.0,
y: 20.0,
width: 100.0,
height: 50.0,
});
let b = h.bounding_box().unwrap();
assert_eq!(b.x, 10.0);
assert_eq!(b.width, 100.0);
}
#[test]
fn content_frame_cache() {
let (h, p) = make_handle();
assert!(h.content_frame().is_none());
let sub_frame = Rc::new(crate::api::frame::Frame::new(
"SUB",
false,
Rc::downgrade(&p),
));
h.set_content_frame(sub_frame.clone());
assert_eq!(h.content_frame().unwrap().id(), "SUB");
}
#[test]
fn scroll_needed_flag() {
let (h, _p) = make_handle();
assert!(!h.scroll_into_view_needed());
h.set_scroll_into_view_needed(true);
assert!(h.scroll_into_view_needed());
}
#[test]
fn dispose_clears_state() {
let (h, _p) = make_handle();
h.set_visible(true);
h.set_enabled(true);
h.set_bounding_box(BoundingBox {
x: 0.0,
y: 0.0,
width: 1.0,
height: 1.0,
});
h.dispose();
assert!(h.is_disposed());
assert!(h.is_visible().is_none());
assert!(h.is_enabled().is_none());
assert!(h.bounding_box().is_none());
}
#[test]
fn owner_frame_returns_frame() {
let (h, p) = make_handle();
let f = h.owner_frame();
assert!(Rc::ptr_eq(&f, &p.main_frame()));
}
#[test]
fn as_js_handle_delegates() {
let (h, _p) = make_handle();
assert_eq!(h.as_js_handle().remote_object_id(), "OBJ-1");
}
}