use crate::error::Result;
use crate::protocol::browser_context::Viewport;
use crate::protocol::{Dialog, Download, Request, ResponseObject, Route, WebSocket, Worker};
use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, AtomicU64};
use std::sync::{Arc, Mutex, RwLock};
use crate::protocol::event_registry::EventRegistry;
#[derive(Clone)]
pub struct Page {
base: ChannelOwnerImpl,
main_frame: crate::protocol::Frame,
route_handlers: Arc<Mutex<Vec<RouteHandlerEntry>>>,
download: Arc<EventRegistry<Download>>,
dialog: Arc<EventRegistry<Dialog>>,
dialog_closed: Arc<EventRegistry<Dialog>>,
request: Arc<EventRegistry<Request>>,
request_finished: Arc<EventRegistry<Request>>,
request_failed: Arc<EventRegistry<Request>>,
response: Arc<EventRegistry<ResponseObject>>,
websocket_handlers: Arc<Mutex<Vec<WebSocketHandler>>>,
ws_route_handlers: Arc<Mutex<Vec<WsRouteHandlerEntry>>>,
viewport: Arc<RwLock<Option<Viewport>>>,
is_closed: Arc<AtomicBool>,
default_timeout_ms: Arc<AtomicU64>,
default_navigation_timeout_ms: Arc<AtomicU64>,
binding_callbacks: Arc<Mutex<HashMap<String, PageBindingCallback>>>,
screencast_frame_handlers: Arc<Mutex<Vec<ScreencastFrameHandler>>>,
screencast_artifact_guid: Arc<Mutex<Option<String>>>,
screencast_save_path: Arc<Mutex<Option<std::path::PathBuf>>>,
filechooser: Arc<EventRegistry<crate::protocol::FileChooser>>,
console: Arc<EventRegistry<crate::protocol::ConsoleMessage>>,
close: Arc<EventRegistry<()>>,
load: Arc<EventRegistry<()>>,
crash: Arc<EventRegistry<()>>,
pageerror: Arc<EventRegistry<String>>,
popup: Arc<EventRegistry<Page>>,
frameattached: Arc<EventRegistry<crate::protocol::Frame>>,
framedetached: Arc<EventRegistry<crate::protocol::Frame>>,
framenavigated: Arc<EventRegistry<crate::protocol::Frame>>,
worker_handlers: Arc<Mutex<Vec<WorkerHandler>>>,
worker_waiters: Arc<Mutex<Vec<tokio::sync::oneshot::Sender<crate::protocol::Worker>>>>,
console_messages_log: Arc<Mutex<Vec<crate::protocol::ConsoleMessage>>>,
page_errors_log: Arc<Mutex<Vec<String>>>,
workers_list: Arc<Mutex<Vec<Worker>>>,
video: Option<crate::protocol::Video>,
locator_handlers: Arc<Mutex<Vec<LocatorHandlerEntry>>>,
}
type RouteHandlerFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
type WebSocketHandlerFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
type WebSocketRouteHandlerFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
#[derive(Clone)]
struct WsRouteHandlerEntry {
pattern: String,
handler:
Arc<dyn Fn(crate::protocol::WebSocketRoute) -> WebSocketRouteHandlerFuture + Send + Sync>,
}
#[derive(Clone)]
struct RouteHandlerEntry {
pattern: String,
handler: Arc<dyn Fn(Route) -> RouteHandlerFuture + Send + Sync>,
in_flight: Arc<crate::protocol::in_flight::InFlight>,
}
type WebSocketHandler = Arc<dyn Fn(WebSocket) -> WebSocketHandlerFuture + Send + Sync>;
type ScreencastFrameHandlerFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
type ScreencastFrameHandler =
Arc<dyn Fn(crate::protocol::ScreencastFrame) -> ScreencastFrameHandlerFuture + Send + Sync>;
type WorkerHandlerFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
type WorkerHandler = Arc<dyn Fn(crate::protocol::Worker) -> WorkerHandlerFuture + Send + Sync>;
type PageBindingCallbackFuture = Pin<Box<dyn Future<Output = serde_json::Value> + Send>>;
type PageBindingCallback =
Arc<dyn Fn(Vec<serde_json::Value>) -> PageBindingCallbackFuture + Send + Sync>;
type LocatorHandlerFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
type LocatorHandlerFn = Arc<dyn Fn(crate::protocol::Locator) -> LocatorHandlerFuture + Send + Sync>;
struct LocatorHandlerEntry {
uid: u32,
selector: String,
handler: LocatorHandlerFn,
times_remaining: Option<u32>,
}
impl LocatorHandlerEntry {
fn take_invocation(&mut self) -> Invocation {
match self.times_remaining {
Some(0) => Invocation {
run: false,
remove: true,
},
Some(ref mut n) => {
*n -= 1;
Invocation {
run: true,
remove: *n == 0,
}
}
None => Invocation {
run: true,
remove: false,
},
}
}
}
#[derive(Debug, PartialEq, Eq)]
struct Invocation {
run: bool,
remove: bool,
}
mod lifecycle;
mod navigation;
mod evaluate;
mod input;
mod events;
mod network;
mod bindings;
mod capture;
mod emulation;
mod response;
mod dispatch;
pub use bindings::AddLocatorHandlerOptions;
pub use capture::{PdfMargin, PdfOptions, PdfOptionsBuilder};
pub use emulation::{
AddScriptTagOptions, AddScriptTagOptionsBuilder, AddStyleTagOptions, AddStyleTagOptionsBuilder,
ColorScheme, EmulateMediaOptions, EmulateMediaOptionsBuilder, ForcedColors, Media,
ReducedMotion,
};
pub use navigation::{GotoOptions, WaitUntil};
pub use network::RouteFromHarOptions;
pub use response::Response;
impl std::fmt::Debug for Page {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Page")
.field("guid", &self.guid())
.field("url", &self.url())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(times: Option<u32>) -> LocatorHandlerEntry {
LocatorHandlerEntry {
uid: 1,
selector: "#x".into(),
handler: Arc::new(|_| Box::pin(async { Ok(()) })),
times_remaining: times,
}
}
#[test]
fn unlimited_handler_runs_every_time_and_is_never_removed() {
let mut e = entry(None);
for _ in 0..3 {
assert_eq!(
e.take_invocation(),
Invocation {
run: true,
remove: false
}
);
}
assert_eq!(e.times_remaining, None);
}
#[test]
fn counted_handler_runs_exactly_times_and_is_removed_on_the_last() {
let mut e = entry(Some(2));
assert_eq!(
e.take_invocation(),
Invocation {
run: true,
remove: false
}
);
assert_eq!(e.times_remaining, Some(1));
assert_eq!(
e.take_invocation(),
Invocation {
run: true,
remove: true
}
);
}
#[test]
fn zero_times_never_runs_and_is_removed_without_underflow() {
let mut e = entry(Some(0));
assert_eq!(
e.take_invocation(),
Invocation {
run: false,
remove: true
}
);
assert_eq!(e.times_remaining, Some(0));
}
}