playwright 0.0.20

Playwright port to Rust
Documentation
use crate::{
    api::{browser::ContextBuilder, browser_type::PersistentContextLauncher},
    imp::{
        browser_type::BrowserType, core::*, impl_future::*, prelude::*, selectors::Selectors,
        utils::Viewport
    }
};
use serde::Deserialize;
use std::{sync::TryLockError, time::Instant};

#[derive(Debug)]
pub(crate) struct Playwright {
    channel: ChannelOwner,
    chromium: Weak<BrowserType>,
    firefox: Weak<BrowserType>,
    webkit: Weak<BrowserType>,
    selectors: Weak<Selectors>,
    devices: Vec<DeviceDescriptor>
}

impl Playwright {
    pub(crate) fn try_new(ctx: &Context, channel: ChannelOwner) -> Result<Self, Error> {
        let i: Initializer = serde_json::from_value(channel.initializer.clone())?;
        let chromium = get_object!(ctx, &i.chromium.guid, BrowserType)?;
        let firefox = get_object!(ctx, &i.firefox.guid, BrowserType)?;
        let webkit = get_object!(ctx, &i.webkit.guid, BrowserType)?;
        let selectors = get_object!(ctx, &i.selectors.guid, Selectors)?;
        let devices = i.device_descriptors;
        Ok(Self {
            channel,
            chromium,
            firefox,
            webkit,
            selectors,
            devices
        })
    }

    pub(crate) fn devices(&self) -> &[DeviceDescriptor] { &self.devices }

    pub(crate) fn device(&self, name: &str) -> Option<&DeviceDescriptor> {
        self.devices.iter().find(|d| d.name == name)
    }

    pub(crate) fn chromium(&self) -> Weak<BrowserType> { self.chromium.clone() }

    pub(crate) fn firefox(&self) -> Weak<BrowserType> { self.firefox.clone() }

    pub(crate) fn webkit(&self) -> Weak<BrowserType> { self.webkit.clone() }

    pub(crate) fn selectors(&self) -> Weak<Selectors> { self.selectors.clone() }

    pub(crate) fn wait_initial_object(conn: &Connection) -> WaitInitialObject {
        WaitInitialObject::new(conn.context())
    }
}

impl RemoteObject for Playwright {
    fn channel(&self) -> &ChannelOwner { &self.channel }
    fn channel_mut(&mut self) -> &mut ChannelOwner { &mut self.channel }
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Initializer {
    chromium: OnlyGuid,
    firefox: OnlyGuid,
    webkit: OnlyGuid,
    android: OnlyGuid,
    selectors: OnlyGuid,
    device_descriptors: Vec<DeviceDescriptor>
}

pub(crate) struct WaitInitialObject {
    ctx: Wm<Context>,
    started: Instant
}

impl WaitInitialObject {
    fn new(ctx: Wm<Context>) -> Self {
        Self {
            ctx,
            started: Instant::now()
        }
    }
}

impl Future for WaitInitialObject {
    type Output = Result<Weak<Playwright>, Error>;

    fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
        let i: &S<Guid> = S::validate("Playwright").unwrap();
        let this = self.get_mut();
        macro_rules! pending {
            () => {{
                cx.waker().wake_by_ref();
                if this.started.elapsed().as_secs() > 10 {
                    return Poll::Ready(Err(Error::InitializationError));
                }
                return Poll::Pending;
            }};
        }
        let rc = upgrade(&this.ctx)?;
        let c = match rc.try_lock() {
            Ok(x) => x,
            Err(TryLockError::WouldBlock) => pending!(),
            Err(e) => Err(e).unwrap()
        };
        match get_object!(c, i, Playwright) {
            Ok(p) => Poll::Ready(Ok(p)),
            Err(_) => pending!()
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct DeviceDescriptor {
    pub name: String,
    pub user_agent: String,
    pub viewport: Viewport,
    pub screen: Option<Viewport>,
    pub device_scale_factor: f64,
    pub is_mobile: bool,
    pub has_touch: bool,
    pub default_browser_type: String
}

impl<'de> Deserialize<'de> for DeviceDescriptor {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>
    {
        #[derive(Deserialize)]
        struct DeviceDescriptorImpl {
            name: String,
            descriptor: Descriptor
        }
        #[derive(Deserialize)]
        #[serde(rename_all = "camelCase")]
        struct Descriptor {
            user_agent: String,
            viewport: Viewport,
            screen: Option<Viewport>,
            device_scale_factor: f64,
            is_mobile: bool,
            has_touch: bool,
            default_browser_type: String
        }
        let DeviceDescriptorImpl {
            name,
            descriptor:
                Descriptor {
                    user_agent,
                    viewport,
                    screen,
                    device_scale_factor,
                    is_mobile,
                    has_touch,
                    default_browser_type
                }
        } = DeviceDescriptorImpl::deserialize(deserializer)?;
        Ok(DeviceDescriptor {
            name,
            user_agent,
            viewport,
            screen,
            device_scale_factor,
            is_mobile,
            has_touch,
            default_browser_type
        })
    }
}

macro_rules! impl_set_device {
    ($device: expr, $builder:expr) => {
        (if let Some(screen) = &$device.screen {
            $builder.screen(screen.clone())
        } else {
            $builder
        })
        .user_agent(&$device.user_agent)
        .viewport(Some($device.viewport.clone()))
        .device_scale_factor($device.device_scale_factor)
        .is_mobile($device.is_mobile)
        .has_touch($device.has_touch)
    };
}

impl DeviceDescriptor {
    pub(crate) fn set_persistent_context<'source, 'b, 'c, 'd, 'e, 'g, 'h, 'i, 'j, 'k, 'l>(
        device: &'source Self,
        builder: PersistentContextLauncher<'b, 'c, 'd, 'e, 'source, 'g, 'h, 'i, 'j, 'k, 'l>
    ) -> PersistentContextLauncher<'b, 'c, 'd, 'e, 'source, 'g, 'h, 'i, 'j, 'k, 'l> {
        impl_set_device!(device, builder)
    }

    pub(crate) fn set_context<'source, 'c, 'd, 'e, 'f, 'g, 'h>(
        device: &'source Self,
        builder: ContextBuilder<'source, 'c, 'd, 'e, 'f, 'g, 'h>
    ) -> ContextBuilder<'source, 'c, 'd, 'e, 'f, 'g, 'h> {
        impl_set_device!(device, builder)
    }
}