slicc-cdp-client 1.42.2

Shared CDP connection for slicc-cdp CLIs
Documentation
pub const FRONT_RESTORE: &str = "Error: the input landed, but the tab that was in front could not be brought back to the front.\n";

#[derive(Debug, Clone, PartialEq)]
pub struct Front {
    own: String,
    window: i64,
    restore: Option<(String, bool)>,
}

impl Cdp {
    pub fn bring_forward(&mut self, session: &str) -> Result<Front, String> {
        let own = self.front_own(session)?;
        let infos = self.front_infos()?;
        let text = |info: &Value, key: &str| info.get(key).and_then(Value::as_str).unwrap_or("").to_string();
        let mine = infos.iter().find(|info| text(info, "targetId") == own).cloned().ok_or_else(|| self.refusal())?;
        if text(&mine, "type") != "page" || self.info_protected(&mine, &infos) { return Err(self.refusal()) }
        let context = text(&mine, "browserContextId");
        let window = self.front_window(&own).map_err(|_| self.refusal())?;
        if self.front_visible(&own) != Some(false) { return Err(self.refusal()) }
        let mut shared = 0;
        let mut visible = Vec::new();
        let mut opaque = Vec::new();
        for info in &infos {
            let id = text(info, "targetId");
            let kind = text(info, "type");
            if id == own || kind == "iframe" || WINDOWLESS.contains(&kind.as_str()) || top_chrome(info) { continue }
            let other = text(info, "browserContextId");
            if !context.is_empty() && !other.is_empty() && other != context { continue }
            let open = !id.is_empty() && url::Url::parse(&text(info, "url")).is_ok() && !self.protected_targets.contains(&id) && !self.info_protected(info, &infos);
            let same = match self.front_window(&id) {
                Ok(found) => found == window,
                Err(_) if open || kind != "page" => continue,
                Err(_) => return Err(self.refusal()),
            };
            if !open && !same { return Err(self.refusal()) }
            if !same { continue }
            shared += 1;
            match (open && kind == "page").then(|| self.front_visible(&id)).flatten() {
                Some(true) => visible.push(id),
                Some(false) => {}
                None => opaque.push(info.clone()),
            }
        }
        let restore = match (visible.as_slice(), opaque.as_slice()) {
            _ if shared == 0 => None,
            ([front], _) => Some((front.clone(), false)),
            ([], [seven]) if text(seven, "type") == "page" && seven_page(&text(seven, "url")) => Some((text(seven, "targetId"), true)),
            _ => return Err(self.refusal()),
        };
        self.front_own(session).ok().filter(|again| again == &own).ok_or_else(|| self.refusal())?;
        self.raw_call("Target.activateTarget", json!({ "targetId": own }), None, DOCUMENT_CHECK)?;
        Ok(Front { own, window, restore })
    }

    pub fn restore_front(&mut self, front: Front) -> Result<(), String> {
        let Some((target, seven)) = front.restore else { return Ok(()) };
        let infos = self.front_infos().map_err(|_| FRONT_RESTORE.to_string())?;
        let text = |info: &Value, key: &str| info.get(key).and_then(Value::as_str).unwrap_or("").to_string();
        let info = infos.iter().find(|info| text(info, "targetId") == target).cloned().ok_or(FRONT_RESTORE.to_string())?;
        let allowed = if seven {
            text(&info, "type") == "page" && seven_page(&text(&info, "url"))
        } else {
            !self.protected_targets.contains(&target) && !self.info_protected(&info, &infos)
        };
        if !allowed || !infos.iter().any(|info| text(info, "targetId") == front.own) || self.front_window(&target).ok() != Some(front.window) {
            return Err(FRONT_RESTORE.to_string());
        }
        self.raw_call("Target.activateTarget", json!({ "targetId": target }), None, DOCUMENT_CHECK).map_err(|_| FRONT_RESTORE.to_string())?;
        Ok(())
    }

    fn front_own(&mut self, session: &str) -> Result<String, String> {
        let own = self.session_target(session).ok_or_else(|| self.refusal())?;
        if self.guard_sessions.contains(session) || self.blocked.contains(session) || self.protected_targets.contains(&own) {
            return Err(self.refusal());
        }
        self.document_state_for(&own, true, None).ok_or_else(|| self.refusal())?;
        if self.blocked.contains(session) { return Err(self.refusal()) }
        Ok(own)
    }

    fn front_infos(&mut self) -> Result<Vec<Value>, String> {
        let listed = self.raw_call("Target.getTargets", json!({}), None, DOCUMENT_CHECK)?;
        listed.get("targetInfos").and_then(Value::as_array).cloned().ok_or_else(|| self.refusal())
    }

    fn front_window(&mut self, target: &str) -> Result<i64, String> {
        let found = self.raw_call("Browser.getWindowForTarget", json!({ "targetId": target }), None, DOCUMENT_CHECK)?;
        let window = found.get("windowId").and_then(Value::as_i64).ok_or_else(|| self.refusal())?;
        if found.pointer("/bounds/windowState").and_then(Value::as_str) == Some("minimized") { return Err(self.refusal()) }
        Ok(window)
    }

    fn front_visible(&mut self, target: &str) -> Option<bool> {
        let attached = self.raw_call("Target.attachToTarget", json!({ "targetId": target, "flatten": true }), None, DOCUMENT_CHECK).ok()?;
        let session = attached.get("sessionId").and_then(Value::as_str).filter(|session| !session.is_empty())?.to_string();
        self.guard_sessions.insert(session.clone());
        let state = (|| {
            let world = self.raw_call("Page.createIsolatedWorld", json!({ "frameId": target, "worldName": "slicc-front" }), Some(&session), DOCUMENT_CHECK).ok()?;
            let context = world.get("executionContextId").cloned()?;
            let read = self.raw_call("Runtime.evaluate", json!({ "expression": "document.visibilityState", "contextId": context, "returnByValue": true }), Some(&session), DOCUMENT_CHECK).ok()?;
            read.pointer("/result/value").and_then(Value::as_str).map(|state| state == "visible")
        })();
        let _ = self.raw_call("Target.detachFromTarget", json!({ "sessionId": session }), None, DOCUMENT_CHECK);
        self.guard_sessions.remove(&session);
        state
    }
}

fn seven_rules() -> &'static slicc_browsers::Seven {
    static SEVEN: std::sync::OnceLock<slicc_browsers::Seven> = std::sync::OnceLock::new();
    SEVEN.get_or_init(|| slicc_browsers::catalog().seven)
}

pub fn seven_page(text: &str) -> bool {
    let Ok(url) = url::Url::parse(text) else { return false };
    let rules = seven_rules();
    if url.scheme() != rules.origin_scheme || !url.username().is_empty() || url.password().is_some() || url.port().is_some() { return false }
    let Some(label) = url.host_str().and_then(|host| host.strip_suffix(".sliccy.ai")) else { return false };
    let bytes = label.as_bytes();
    !bytes.is_empty() && bytes.len() <= 63
        && bytes.iter().all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || *byte == b'-')
        && bytes[0] != b'-' && bytes[bytes.len() - 1] != b'-'
        && rules.is_seven_label(label)
}