use crate::error::Result;
use crate::protocol::BrowserType;
use crate::server::channel::Channel;
use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
use crate::server::connection::{ConnectionExt, ConnectionLike};
use crate::server::playwright_server::PlaywrightServer;
use parking_lot::Mutex;
use serde_json::Value;
use std::any::Any;
use std::sync::Arc;
#[derive(Clone)]
pub struct Playwright {
base: ChannelOwnerImpl,
chromium: BrowserType,
firefox: BrowserType,
webkit: BrowserType,
server: Arc<Mutex<Option<PlaywrightServer>>>,
}
impl Playwright {
pub async fn launch() -> Result<Self> {
use crate::server::connection::Connection;
use crate::server::playwright_server::PlaywrightServer;
use crate::server::transport::PipeTransport;
tracing::debug!("Launching Playwright server");
let mut server = PlaywrightServer::launch().await?;
let stdin = server.process.stdin.take().ok_or_else(|| {
crate::error::Error::ServerError("Failed to get server stdin".to_string())
})?;
let stdout = server.process.stdout.take().ok_or_else(|| {
crate::error::Error::ServerError("Failed to get server stdout".to_string())
})?;
tracing::debug!("Creating transport and connection");
let (transport, message_rx) = PipeTransport::new(stdin, stdout);
let (sender, receiver) = transport.into_parts();
let connection: Arc<Connection> = Arc::new(Connection::new(sender, receiver, message_rx));
let conn_for_loop: Arc<Connection> = Arc::clone(&connection);
tokio::spawn(async move {
conn_for_loop.run().await;
});
tracing::debug!("Initializing Playwright protocol");
let playwright_obj = connection.initialize_playwright().await?;
let guid = playwright_obj.guid().to_string();
let mut playwright: Playwright = connection.get_typed::<Playwright>(&guid).await?;
playwright.server = Arc::new(Mutex::new(Some(server)));
Ok(playwright)
}
pub async fn new(
connection: Arc<dyn ConnectionLike>,
type_name: String,
guid: Arc<str>,
initializer: Value,
) -> Result<Self> {
let base = ChannelOwnerImpl::new(
ParentOrConnection::Connection(connection.clone()),
type_name,
guid,
initializer.clone(),
);
let chromium_guid = initializer["chromium"]["guid"].as_str().ok_or_else(|| {
crate::error::Error::ProtocolError(
"Playwright initializer missing 'chromium.guid'".to_string(),
)
})?;
let firefox_guid = initializer["firefox"]["guid"].as_str().ok_or_else(|| {
crate::error::Error::ProtocolError(
"Playwright initializer missing 'firefox.guid'".to_string(),
)
})?;
let webkit_guid = initializer["webkit"]["guid"].as_str().ok_or_else(|| {
crate::error::Error::ProtocolError(
"Playwright initializer missing 'webkit.guid'".to_string(),
)
})?;
let chromium: BrowserType = connection.get_typed::<BrowserType>(chromium_guid).await?;
let firefox: BrowserType = connection.get_typed::<BrowserType>(firefox_guid).await?;
let webkit: BrowserType = connection.get_typed::<BrowserType>(webkit_guid).await?;
Ok(Self {
base,
chromium,
firefox,
webkit,
server: Arc::new(Mutex::new(None)), })
}
pub fn chromium(&self) -> &BrowserType {
&self.chromium
}
pub fn firefox(&self) -> &BrowserType {
&self.firefox
}
pub fn webkit(&self) -> &BrowserType {
&self.webkit
}
pub async fn shutdown(&self) -> Result<()> {
let server = self.server.lock().take();
if let Some(server) = server {
tracing::debug!("Shutting down Playwright server");
server.shutdown().await?;
}
Ok(())
}
}
impl ChannelOwner for Playwright {
fn guid(&self) -> &str {
self.base.guid()
}
fn type_name(&self) -> &str {
self.base.type_name()
}
fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
self.base.parent()
}
fn connection(&self) -> Arc<dyn ConnectionLike> {
self.base.connection()
}
fn initializer(&self) -> &Value {
self.base.initializer()
}
fn channel(&self) -> &Channel {
self.base.channel()
}
fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
self.base.dispose(reason)
}
fn adopt(&self, child: Arc<dyn ChannelOwner>) {
self.base.adopt(child)
}
fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
self.base.add_child(guid, child)
}
fn remove_child(&self, guid: &str) {
self.base.remove_child(guid)
}
fn on_event(&self, method: &str, params: Value) {
self.base.on_event(method, params)
}
fn was_collected(&self) -> bool {
self.base.was_collected()
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl Drop for Playwright {
fn drop(&mut self) {
if let Some(mut server) = self.server.lock().take() {
tracing::debug!("Drop: Force-killing Playwright server");
#[cfg(windows)]
{
drop(server.process.stdin.take());
drop(server.process.stdout.take());
drop(server.process.stderr.take());
}
if let Err(e) = server.process.start_kill() {
tracing::warn!("Failed to kill Playwright server in Drop: {}", e);
}
}
}
}
impl std::fmt::Debug for Playwright {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Playwright")
.field("guid", &self.guid())
.field("chromium", &self.chromium().name())
.field("firefox", &self.firefox().name())
.field("webkit", &self.webkit().name())
.finish()
}
}