slicc-cdp-client 1.41.21

Shared CDP connection for slicc-cdp CLIs
Documentation
use super::{unguarded, Navigation, DOCUMENT_CHECK, GUARD_SETUP};

struct RendererCrashBackend {
    inbox: VecDeque<String>,
    crash: Value,
    reply: bool,
    calls: Rc<RefCell<Vec<Value>>>,
}

impl Transport for RendererCrashBackend {
    fn send_text(&mut self, text: &str) -> Result<(), String> {
        let message: Value = serde_json::from_str(text).unwrap();
        self.calls.borrow_mut().push(message.clone());
        if message["method"] == "Network.enable" {
            self.inbox.push_back(self.crash.to_string());
            if !self.reply { return Ok(()) }
        }
        self.inbox.push_back(json!({"id":message["id"],"result":{}}).to_string());
        Ok(())
    }

    fn recv_text(&mut self, _: Instant) -> Result<Option<String>, String> {
        Ok(Some(self.inbox.pop_front().expect("waited after the renderer crash")))
    }
}

fn renderer_crash_client(crash: Value, reply: bool) -> (Cdp, Rc<RefCell<Vec<Value>>>) {
    let calls = Rc::new(RefCell::new(Vec::new()));
    let mut cdp = Cdp::from_transport(Box::new(RendererCrashBackend {
        inbox: VecDeque::new(), crash, reply, calls: Rc::clone(&calls),
    }));
    cdp.session_targets.insert("S".into(), "T".into());
    cdp.guards.insert("T".into(), "G".into());
    cdp.guard_sessions.insert("G".into());
    (cdp, calls)
}

#[test]
fn renderer_crash_on_the_guard_fails_without_another_receive_or_an_ack() {
    let (mut cdp, calls) = renderer_crash_client(json!({"method":"Inspector.targetCrashed","sessionId":"G","params":{}}), false);
    assert_eq!(cdp.raw_call("Network.enable", json!({}), Some("S"), GUARD_SETUP).unwrap_err(), "slicc: refused: renderer crashed\n");
    assert_eq!(calls.borrow().iter().filter(|call| call["method"] == "Network.enable").count(), 1);
}

#[test]
fn renderer_crash_reasons_take_precedence_over_late_success_and_detach_refusals() {
    for crash in [
        json!({"method":"Inspector.targetCrashed","sessionId":"S","params":{}}),
        json!({"method":"Inspector.detached","sessionId":"G","params":{"reason":"renderer crashed"}}),
        json!({"method":"Target.detachedFromTarget","params":{"sessionId":"G","reason":"target_crashed"}}),
        json!({"method":"Target.detachedFromTarget","params":{"sessionId":"S","reason":"crashed"}}),
    ] {
        let (mut cdp, calls) = renderer_crash_client(crash, true);
        assert_eq!(cdp.raw_call("Network.enable", json!({}), Some("S"), GUARD_SETUP).unwrap_err(), "slicc: refused: renderer crashed\n");
        let from = calls.borrow().len();
        assert_eq!(cdp.raw_call("Network.enable", json!({}), Some("S"), GUARD_SETUP).unwrap_err(), "slicc: refused: renderer crashed\n");
        assert_eq!(calls.borrow().len(), from);
        assert_eq!(cdp.call("Network.enable", json!({}), Some("S")).unwrap_err(), "slicc: refused: renderer crashed\n");
        assert_eq!(cdp.settled_refusal("Network.enable", &json!({}), Some("S")).unwrap(), "slicc: refused: renderer crashed\n");
        assert_eq!(cdp.settled_refusal("Target.attachToTarget", &json!({"targetId":"T","flatten":true}), None).unwrap(), "slicc: refused: renderer crashed\n");
        cdp.raw_call("Browser.getVersion", json!({}), None, DOCUMENT_CHECK).unwrap();
    }
    assert_eq!(unguarded("slicc: refused: renderer crashed\n".into()), "slicc: refused: renderer crashed\n");
}

#[test]
fn renderer_crash_unrelated_targets_and_noncrash_detaches_do_not_abort_healthy_calls() {
    for crash in [
        json!({"method":"Inspector.targetCrashed","sessionId":"OTHER","params":{}}),
        json!({"method":"Inspector.targetCrashed","params":{}}),
        json!({"method":"Inspector.detached","sessionId":"G","params":{"reason":"replaced_with_devtools"}}),
        json!({"method":"Inspector.detached","sessionId":"G","params":{"reason":"Render process gone."}}),
        json!({"method":"Inspector.detached","sessionId":"G","params":{"reason":"render_process_gone"}}),
        json!({"method":"Runtime.executionContextsCleared","sessionId":"G","params":{"reason":"crashed"}}),
    ] {
        let (mut cdp, _) = renderer_crash_client(crash, true);
        cdp.session_targets.insert("OTHER".into(), "U".into());
        cdp.raw_call("Network.enable", json!({}), Some("S"), DOCUMENT_CHECK).unwrap();
        cdp.raw_call("Browser.getVersion", json!({}), None, DOCUMENT_CHECK).unwrap();
    }
}

#[test]
fn renderer_crash_queued_replies_and_navigation_replies_cannot_report_success() {
    let (mut cdp, _) = renderer_crash_client(Value::Null, true);
    cdp.absorb(json!({"method":"Inspector.targetCrashed","sessionId":"G","params":{}}));
    cdp.call_replies.insert(700, json!({"id":700,"result":{"value":"late success"}}));
    assert_eq!(cdp.raw_reply("Runtime.evaluate", 700, Some("S"), Duration::ZERO, true).unwrap_err(), "slicc: refused: renderer crashed\n");
    cdp.navigations.insert(701, Navigation {
        params: json!({"url":"https://example.test/"}), session: Some("S".into()),
        reply: Some(json!({"id":701,"result":{"frameId":"F","loaderId":"L"}})), detached: false,
    });
    assert_eq!(cdp.poll_navigation(701).unwrap().unwrap_err(), "slicc: refused: renderer crashed\n");
    assert!(cdp.poll_navigation(701).is_none());
}