use std::cell::RefCell;
use std::rc::{Rc, Weak};
use serde_json::Value;
use super::event_emitter::{EventEmitter, EventEmitterInner};
use super::page::Page;
#[derive(Debug, Clone)]
pub struct ExecutionContext {
id: String,
name: String,
origin: String,
aux_data: RefCell<Option<Value>>,
}
impl ExecutionContext {
pub fn new(id: String) -> Self {
Self {
id,
name: String::new(),
origin: String::new(),
aux_data: RefCell::new(None),
}
}
pub fn with_name_origin(
id: String,
name: impl Into<String>,
origin: impl Into<String>,
) -> Self {
Self {
id,
name: name.into(),
origin: origin.into(),
aux_data: RefCell::new(None),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn origin(&self) -> &str {
&self.origin
}
pub fn aux_data(&self) -> Option<Value> {
self.aux_data.borrow().clone()
}
pub fn set_aux_data(&self, data: Value) {
*self.aux_data.borrow_mut() = Some(data);
}
}
pub struct Frame {
id: String,
name: RefCell<String>,
url: RefCell<String>,
parent: RefCell<Option<Weak<Frame>>>,
children: RefCell<Vec<Rc<Frame>>>,
execution_context: RefCell<Option<Rc<ExecutionContext>>>,
detached: RefCell<bool>,
is_main: bool,
page: Weak<Page>,
events: Rc<EventEmitterInner>,
}
impl std::fmt::Debug for Frame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Frame")
.field("id", &self.id)
.field("name", &self.name.borrow())
.field("url", &self.url.borrow())
.field("is_main", &self.is_main)
.field("detached", &self.detached.borrow())
.field("child_count", &self.children.borrow().len())
.finish()
}
}
impl Frame {
pub fn new(id: impl Into<String>, is_main: bool, page: Weak<Page>) -> Self {
Self {
id: id.into(),
name: RefCell::new(String::new()),
url: RefCell::new(String::new()),
parent: RefCell::new(None),
children: RefCell::new(Vec::new()),
execution_context: RefCell::new(None),
detached: RefCell::new(false),
is_main,
page,
events: Rc::new(EventEmitterInner::new()),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn is_main_frame(&self) -> bool {
self.is_main
}
pub fn name(&self) -> String {
self.name.borrow().clone()
}
pub fn set_name(&self, name: impl Into<String>) {
*self.name.borrow_mut() = name.into();
}
pub fn url(&self) -> String {
self.url.borrow().clone()
}
pub fn set_url(&self, url: impl Into<String>) {
*self.url.borrow_mut() = url.into();
}
pub fn parent_frame(&self) -> Option<Rc<Frame>> {
self.parent.borrow().as_ref().and_then(|w| w.upgrade())
}
pub fn add_child(&self, child: Rc<Frame>, self_ref: Weak<Frame>) {
*child.parent.borrow_mut() = Some(self_ref);
self.children.borrow_mut().push(child);
}
pub fn remove_child(&self, child_id: &str) {
self.children.borrow_mut().retain(|c| c.id() != child_id);
}
pub fn child_frames(&self) -> Vec<Rc<Frame>> {
self.children.borrow().clone()
}
pub fn execution_context(&self) -> Option<Rc<ExecutionContext>> {
self.execution_context.borrow().clone()
}
pub fn set_execution_context(&self, ctx: Rc<ExecutionContext>) {
*self.execution_context.borrow_mut() = Some(ctx);
}
pub fn is_detached(&self) -> bool {
*self.detached.borrow()
}
pub fn set_detached(&self, detached: bool) {
*self.detached.borrow_mut() = detached;
}
pub fn page(&self) -> Option<Rc<Page>> {
self.page.upgrade()
}
pub fn events(&self) -> &Rc<EventEmitterInner> {
&self.events
}
}
impl EventEmitter for Frame {
delegate_event_emitter!(self, events);
}
#[cfg(test)]
mod tests {
use super::super::event_emitter::EventHandler;
use super::*;
use crate::api::browser::Browser as HighLevelBrowser;
use crate::api::browser_context::BrowserContext;
use crate::api::page::Page;
use std::rc::Rc;
fn make_frame(is_main: bool, page: Weak<Page>) -> Rc<Frame> {
Rc::new(Frame::new("FRAME-1", is_main, page))
}
fn make_page() -> Rc<Page> {
let browser = Rc::new(HighLevelBrowser::new_for_test("ws://x"));
let ctx = HighLevelBrowser::new_context_for_test(&browser);
BrowserContext::new_page_for_test(&ctx)
}
#[test]
fn frame_id_and_is_main() {
let page = make_page();
let f = make_frame(true, Rc::downgrade(&page));
assert_eq!(f.id(), "FRAME-1");
assert!(f.is_main_frame());
}
#[test]
fn frame_name_local() {
let page = make_page();
let f = make_frame(true, Rc::downgrade(&page));
assert_eq!(f.name(), "");
f.set_name("myIframe");
assert_eq!(f.name(), "myIframe");
}
#[test]
fn frame_url_local() {
let page = make_page();
let f = make_frame(true, Rc::downgrade(&page));
assert_eq!(f.url(), "");
f.set_url("https://example.com");
assert_eq!(f.url(), "https://example.com");
}
#[test]
fn frame_detached_flag() {
let page = make_page();
let f = make_frame(true, Rc::downgrade(&page));
assert!(!f.is_detached());
f.set_detached(true);
assert!(f.is_detached());
}
#[test]
fn frame_execution_context_local() {
let page = make_page();
let f = make_frame(true, Rc::downgrade(&page));
assert!(f.execution_context().is_none());
let ctx = Rc::new(ExecutionContext::new("CTX-1".into()));
f.set_execution_context(ctx.clone());
assert_eq!(f.execution_context().unwrap().id(), "CTX-1");
}
#[test]
fn frame_parent_and_children() {
let page = make_page();
let parent = Rc::new(Frame::new("P", true, Rc::downgrade(&page)));
let child = Rc::new(Frame::new("C", false, Rc::downgrade(&page)));
parent.add_child(child.clone(), Rc::downgrade(&parent));
assert!(parent.parent_frame().is_none());
assert_eq!(parent.child_frames().len(), 1);
assert_eq!(
child.parent_frame().map(|f| f.id().to_string()),
Some("P".into())
);
parent.remove_child("C");
assert_eq!(parent.child_frames().len(), 0);
assert!(child.parent_frame().is_some());
}
#[test]
fn frame_page_upgrade() {
let page = make_page();
let f = make_frame(true, Rc::downgrade(&page));
assert!(f.page().is_some());
drop(page);
assert!(f.page().is_none());
}
#[test]
fn execution_context_data_holder() {
let ctx = ExecutionContext::with_name_origin("CTX-1".into(), "main", "https://x");
assert_eq!(ctx.id(), "CTX-1");
assert_eq!(ctx.name(), "main");
assert_eq!(ctx.origin(), "https://x");
assert!(ctx.aux_data().is_none());
ctx.set_aux_data(Value::from(42));
assert_eq!(ctx.aux_data(), Some(Value::from(42)));
}
#[test]
fn frame_event_emitter_via_inner() {
let page = make_page();
let f = make_frame(true, Rc::downgrade(&page));
let counter = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
let c = counter.clone();
let h: EventHandler = std::sync::Arc::new(move |_| {
c.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
});
f.events().on("fnav", h);
f.events().emit("fnav", &[]);
assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
}
}