slicc-cdp-client 1.41.21

Shared CDP connection for slicc-cdp CLIs
Documentation
use crate::cdp::{self as client, Cdp};
use serde_json::{json, Value};
use std::time::{Duration, Instant};

fn live(name: &str) -> String {
    std::env::var(name).unwrap_or_else(|_| panic!("{name} is not set"))
}

fn attached(cdp: &Cdp) -> Vec<(String, String)> {
    cdp.events()
        .iter()
        .filter(|event| event["method"] == json!("Target.attachedToTarget"))
        .map(|event| {
            (
                event
                    .pointer("/params/targetInfo/targetId")
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string(),
                event
                    .pointer("/params/sessionId")
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string(),
            )
        })
        .collect()
}

fn pump_for(cdp: &mut Cdp, span: Duration) {
    let until = Instant::now() + span;
    while Instant::now() < until {
        let _ = cdp.pump(Instant::now() + Duration::from_millis(100));
    }
}

fn refused(result: Result<Value, String>, what: &str) {
    match result {
        Ok(value) => panic!("{what} was allowed: {value}"),
        Err(message) => assert!(message.starts_with("slicc: refused: "), "{what}: {message}"),
    }
}

fn page_ids(cdp: &mut Cdp) -> Vec<String> {
    cdp.call("Target.getTargets", json!({}), None).unwrap()["targetInfos"]
        .as_array()
        .unwrap()
        .iter()
        .filter(|info| info["type"] == json!("page"))
        .filter_map(|info| info["targetId"].as_str().map(str::to_string))
        .collect()
}

fn timed_out_guard(ws: &str) -> (Cdp, String) {
    let mut cdp = Cdp::connect(ws).unwrap();
    let before = page_ids(&mut cdp);
    let created = cdp.call("Target.createTarget", json!({ "url": "about:blank" }), None);
    refused(created, "createTarget whose guard attach timed out");
    let mut abandoned: Vec<String> = Vec::new();
    let until = Instant::now() + Duration::from_secs(5);
    while abandoned.is_empty() && Instant::now() < until {
        abandoned = page_ids(&mut cdp)
            .into_iter()
            .filter(|id| !before.contains(id))
            .collect();
        let _ = cdp.pump(Instant::now() + Duration::from_millis(100));
    }
    pump_for(&mut cdp, Duration::from_secs(3));
    (cdp, abandoned.into_iter().next().unwrap_or_default())
}

#[test]
#[ignore = "needs SLICC_RACE_PASS_WS from test/integration/guard-attach-race.test.mjs"]
fn live_a_guarded_create_or_attach_surfaces_only_the_callers_session() {
    let mut cdp = Cdp::connect(&live("SLICC_RACE_PASS_WS")).unwrap();
    let target = cdp
        .call("Target.createTarget", json!({ "url": "about:blank" }), None)
        .unwrap()["targetId"]
        .as_str()
        .unwrap()
        .to_string();
    pump_for(&mut cdp, Duration::from_millis(500));
    assert_eq!(
        attached(&cdp),
        Vec::new(),
        "createTarget surfaced an attach"
    );
    let session = client::attach(&mut cdp, &target).unwrap();
    pump_for(&mut cdp, Duration::from_millis(500));
    assert_eq!(attached(&cdp), vec![(target.clone(), session)]);
    cdp.call("Target.closeTarget", json!({ "targetId": target }), None)
        .unwrap();
}

#[test]
#[ignore = "needs SLICC_RACE_DELAY_WS from test/integration/guard-attach-race.test.mjs"]
fn live_b_a_timed_out_guard_attach_leaves_no_session_and_its_target_refused() {
    let (mut cdp, target) = timed_out_guard(&live("SLICC_RACE_DELAY_WS"));
    assert_eq!(
        attached(&cdp),
        Vec::new(),
        "the late guard attach reached the caller"
    );
    if !target.is_empty() {
        refused(
            client::attach(&mut cdp, &target).map(Value::String),
            "attach to the abandoned target",
        );
    }
}

#[test]
#[ignore = "needs SLICC_RACE_NOCLOSE_WS from test/integration/guard-attach-race.test.mjs"]
fn live_c_a_timed_out_guard_attach_with_a_failed_close_stays_refused() {
    let (mut cdp, target) = timed_out_guard(&live("SLICC_RACE_NOCLOSE_WS"));
    assert!(
        !target.is_empty(),
        "closeTarget was forced to fail, so the target must still exist"
    );
    let leaked = attached(&cdp);
    for (_, session) in &leaked {
        refused(
            cdp.call_for(
                "Runtime.evaluate",
                json!({ "expression": "location.href", "returnByValue": true }),
                Some(session),
                Duration::from_secs(5),
            ),
            "Runtime.evaluate on a surfaced guard session",
        );
    }
    assert_eq!(
        leaked,
        Vec::new(),
        "the late guard attach reached the caller"
    );
    refused(
        client::attach(&mut cdp, &target).map(Value::String),
        "attach to the unguarded target",
    );
    refused(
        cdp.call("Target.activateTarget", json!({ "targetId": target }), None),
        "activateTarget on the unguarded target",
    );
}