pub use cdp_server::{CdpError, CdpEvent, CdpMessage, CdpResponse};
use serde_json::Value;
use crate::servo_bridge::{BridgeCommand, BridgeSender};
const ERR_METHOD_NOT_FOUND: i64 = -32601;
const ERR_PARSE_ERROR: i64 = -32700;
const ERR_INVALID_PARAMS: i64 = -32602;
const ERR_NOT_SUPPORTED: i64 = -32000;
fn not_supported(method: &str, reason: &str) -> CdpError {
CdpError {
code: ERR_NOT_SUPPORTED,
message: format!("'{method}' not supported: {reason}"),
}
}
pub fn parse_message(raw: &str) -> Option<CdpMessage> {
serde_json::from_str(raw).ok()
}
pub fn serialize_response(resp: &CdpResponse) -> String {
serde_json::to_string(resp).unwrap_or_else(|_| {
format!(r#"{{"id":null,"error":{{"code":{ERR_PARSE_ERROR},"message":"serialize error"}}}}"#)
})
}
pub fn serialize_event(ev: &CdpEvent) -> String {
serde_json::to_string(ev).unwrap_or_else(|_| "{}".into())
}
fn ok_response(id: Option<i64>, result: Value) -> CdpResponse {
CdpResponse {
id,
result: Some(result),
error: None,
}
}
fn error_response(id: Option<i64>, code: i64, message: impl Into<String>) -> CdpResponse {
CdpResponse {
id,
result: None,
error: Some(CdpError {
code,
message: message.into(),
}),
}
}
pub fn handle_command(
msg: CdpMessage,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> CdpResponse {
let parts: Vec<&str> = msg.method.splitn(2, '.').collect();
let domain = parts.first().copied().unwrap_or("");
let command = parts.get(1).copied().unwrap_or("");
let result = match domain {
"Target" => handle_target(command, target_id, params, bridge),
"Page" => handle_page(command, target_id, params, bridge),
"Runtime" => handle_runtime(command, target_id, params, bridge),
"DOM" => handle_dom(command, target_id, params, bridge),
"Network" => handle_network(command, target_id, params, bridge),
"CSS" => handle_css(command, target_id, params, bridge),
"Emulation" => handle_emulation(command, target_id, params, bridge),
"Input" => handle_input(command, target_id, params, bridge),
"Overlay" => handle_overlay(command),
"Debugger" => handle_debugger(command, target_id, params, bridge),
"Log" => handle_log(command),
"Fetch" => handle_fetch(command, params),
"Storage" => handle_storage(command, target_id, params, bridge),
"Security" => handle_security(command, target_id, params, bridge),
"Profiler" => handle_profiler(command, target_id, params, bridge),
"HeapProfiler" => handle_heap_profiler(command, target_id, bridge),
"Memory" => handle_memory(command, target_id, bridge),
"Performance" => handle_performance(command, target_id, bridge),
"SystemInfo" => handle_system_info(command),
"ServiceWorker" => handle_service_worker(command, target_id, params, bridge),
"Browser" => handle_browser(command),
_ => Err(CdpError {
code: ERR_METHOD_NOT_FOUND,
message: format!("'{}' wasn't found", msg.method),
}),
};
match result {
Ok(r) => ok_response(msg.id, r),
Err(e) => error_response(msg.id, e.code, e.message),
}
}
type HandlerResult = Result<Value, CdpError>;
fn next_cdp_identifier(prefix: &str) -> String {
static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
let n = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
format!("{prefix}-{n:016x}")
}
fn params_str(params: &Option<Value>, key: &str) -> String {
params
.as_ref()
.and_then(|p| p.get(key))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string()
}
fn bridge_send(bridge: Option<&BridgeSender>, cmd: BridgeCommand) -> HandlerResult {
match bridge {
Some(b) => {
let resp = b.send(cmd);
resp.result.map_err(|e| CdpError {
code: -32603,
message: e,
})
}
None => Err(CdpError {
code: -32603,
message: "no servo bridge connected".into(),
}),
}
}
fn ok_empty() -> HandlerResult {
Ok(serde_json::json!({}))
}
fn eval_json(bridge: Option<&BridgeSender>, tid: &str, expression: &str) -> HandlerResult {
let resp = bridge_send(
bridge,
BridgeCommand::EvaluateJs {
target_id: tid.to_string(),
expression: expression.to_string(),
return_by_value: true,
},
)?;
resp.get("result")
.and_then(|r| r.get("value"))
.cloned()
.filter(|v| v.is_object())
.ok_or_else(|| CdpError {
code: ERR_NOT_SUPPORTED,
message: "page query failed: target did not return a JSON document".into(),
})
}
fn live_target_info(target_id: &str, bridge: Option<&BridgeSender>) -> Value {
let title = bridge
.and_then(|b| {
b.send(BridgeCommand::GetTitle {
target_id: target_id.to_string(),
})
.result
.ok()
})
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_else(|| "Bao".into());
let url = bridge
.and_then(|b| {
b.send(BridgeCommand::GetUrl {
target_id: target_id.to_string(),
})
.result
.ok()
})
.and_then(|v| v.as_str().map(|s| s.to_string()))
.unwrap_or_else(|| "about:blank".into());
serde_json::json!({
"targetId": target_id,
"type": "page",
"title": title,
"url": url,
"attached": true,
"browserContextId": "bao-default-context",
})
}
fn handle_target(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
match command {
"getTargets" | "getTargetTargets" => {
let mut target_infos: Vec<Value> = Vec::new();
let listed = bridge
.and_then(|b| b.send(BridgeCommand::ListTargets).result.ok())
.and_then(|v| v.as_array().cloned());
match listed {
Some(entries) => {
for entry in entries {
let id = entry.get("id").and_then(|v| v.as_str()).unwrap_or("");
target_infos.push(live_target_info(id, bridge));
}
}
None => target_infos.push(live_target_info(target_id, bridge)),
}
if let Some(b) = bridge {
if let Ok(worker_resp) = b
.send(BridgeCommand::ListWorkerTargets {
target_id: target_id.to_string(),
})
.result
{
if let Some(workers) =
worker_resp.get("workerTargets").and_then(|v| v.as_array())
{
for w in workers {
target_infos.push(w.clone());
}
}
}
}
Ok(serde_json::json!({ "targetInfos": target_infos }))
}
"createTarget" => {
let url = params
.as_ref()
.and_then(|v| v.get("url"))
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.unwrap_or("about:blank");
bridge_send(
bridge,
BridgeCommand::CreateTarget {
url: url.to_string(),
},
)
}
"closeTarget" => {
let tid = params
.as_ref()
.and_then(|v| v.get("targetId"))
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.unwrap_or(target_id);
bridge_send(
bridge,
BridgeCommand::ClosePage {
target_id: tid.to_string(),
},
)?;
Ok(serde_json::json!({ "success": true }))
}
"setAutoAttach" | "setDiscoverTargets" => ok_empty(),
"getTargetInfo" => {
let tid = params
.as_ref()
.and_then(|v| v.get("targetId"))
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.unwrap_or(target_id);
Ok(serde_json::json!({ "targetInfo": live_target_info(tid, bridge) }))
}
"attachToTarget" => Err(not_supported(
"Target.attachToTarget",
"session minting requires the WS session registry (bao_browser); the stateless internal backend has no session table",
)),
"detachFromTarget" | "sendMessageToTarget" => Err(not_supported(
&format!("Target.{command}"),
"session routing requires the WS session registry (bao_browser); the stateless internal backend has no session table",
)),
_ => Err(CdpError {
code: -32601,
message: format!("'Target.{}' wasn't found", command),
}),
}
}
fn handle_page(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" | "disable" => ok_empty(),
"setLifecycleEventsEnabled" => ok_empty(),
"navigate" => {
let url = params
.as_ref()
.and_then(|v| v.get("url"))
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.unwrap_or("about:blank");
bridge_send(
bridge,
BridgeCommand::Navigate {
target_id: tid,
url: url.to_string(),
},
)
}
"reload" => {
let ignore_cache = params
.as_ref()
.and_then(|p| p.get("ignoreCache"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
bridge_send(
bridge,
BridgeCommand::Reload {
target_id: tid,
ignore_cache,
},
)
}
"getFrameTree" => {
let mut frame = eval_json(
bridge,
&tid,
r#"(function(){ return JSON.stringify({
url: location.href,
mimeType: document.contentType,
name: window.name,
securityOrigin: location.origin
}); })()"#,
)?;
if let Some(obj) = frame.as_object_mut() {
obj.insert("id".into(), serde_json::json!(tid));
}
Ok(serde_json::json!({ "frameTree": { "frame": frame } }))
}
"getNavigationHistory" => Err(not_supported(
"Page.getNavigationHistory",
"servo WebView does not expose session-history entry enumeration (only can_go_back/go_forward traversal)",
)),
"captureScreenshot" => {
let format = params
.as_ref()
.and_then(|p| p.get("format"))
.and_then(|v| v.as_str())
.unwrap_or("png")
.to_string();
let quality = params
.as_ref()
.and_then(|p| p.get("quality"))
.and_then(|v| v.as_u64())
.map(|q| q as u8);
bridge_send(
bridge,
BridgeCommand::TakeScreenshot {
target_id: tid,
format,
quality,
},
)
}
"setContent" => {
let html = params_str(params, "html");
if html.is_empty() {
return Err(CdpError {
code: ERR_INVALID_PARAMS,
message: "'Page.setContent' requires a non-empty html param".into(),
});
}
let js = format!(
r#"(function() {{ document.open(); document.write({}); document.close(); }})()"#,
serde_json::to_string(&html).unwrap_or_default(),
);
bridge_send(
bridge,
BridgeCommand::EvaluateJs {
target_id: tid,
expression: js,
return_by_value: true,
},
)?;
ok_empty()
}
"close" => {
bridge_send(bridge, BridgeCommand::ClosePage { target_id: tid })?;
ok_empty()
}
"bringToFront" => {
bridge_send(
bridge,
BridgeCommand::EvaluateJs {
target_id: tid,
expression: "window.focus()".into(),
return_by_value: true,
},
)?;
ok_empty()
}
"getLayoutMetrics" => {
let m = eval_json(
bridge,
&tid,
r#"(function(){ var d = document.documentElement;
return JSON.stringify({
iw: window.innerWidth, ih: window.innerHeight,
cw: d.scrollWidth, ch: d.scrollHeight
}); })()"#,
)?;
Ok(serde_json::json!({
"layoutViewport": { "x": 0, "y": 0, "width": m["iw"], "height": m["ih"] },
"contentSize": { "x": 0, "y": 0, "width": m["cw"], "height": m["ch"] },
"cssLayoutViewport": { "x": 0, "y": 0, "width": m["iw"], "height": m["ih"] },
"cssContentSize": { "x": 0, "y": 0, "width": m["cw"], "height": m["ch"] }
}))
}
"addScriptToEvaluateOnNewDocument" => {
let source = params_str(params, "source");
if source.is_empty() {
return Ok(serde_json::json!({
"identifier": next_cdp_identifier("script")
}));
}
bridge_send(
bridge,
BridgeCommand::AddScriptToEvaluateOnNewDocument {
target_id: tid,
source,
},
)
}
"removeScriptToEvaluateOnNewDocument" => {
let identifier = params_str(params, "identifier");
if identifier.is_empty() {
return Err(CdpError {
code: ERR_INVALID_PARAMS,
message: "'Page.removeScriptToEvaluateOnNewDocument' requires an identifier param"
.into(),
});
}
Err(not_supported(
"Page.removeScriptToEvaluateOnNewDocument",
"added scripts are not kept in a removable registry",
))
}
_ => Err(CdpError {
code: -32601,
message: format!("'Page.{}' wasn't found", command),
}),
}
}
fn handle_runtime(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" => ok_empty(),
"disable" => ok_empty(),
"evaluate" => {
let expression = params
.as_ref()
.and_then(|p| p.get("expression"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let return_by_value = params
.as_ref()
.and_then(|p| p.get("returnByValue"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
if bridge.is_some() && !expression.is_empty() {
bridge_send(
bridge,
BridgeCommand::EvaluateJs {
target_id: tid,
expression,
return_by_value,
},
)
} else {
Ok(
serde_json::json!({ "result": { "type": "undefined" }, "exceptionDetails": null }),
)
}
}
"callFunctionOn" => {
let object_id = params
.as_ref()
.and_then(|p| p.get("objectId"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let function_declaration = params
.as_ref()
.and_then(|p| p.get("functionDeclaration"))
.and_then(|v| v.as_str())
.unwrap_or("function(){}")
.to_string();
let arguments = params.as_ref().and_then(|p| p.get("arguments")).cloned();
let return_by_value = params
.as_ref()
.and_then(|p| p.get("returnByValue"))
.and_then(|v| v.as_bool());
let execution_context_id = params
.as_ref()
.and_then(|p| p.get("executionContextId"))
.and_then(|v| v.as_i64());
let await_promise = params
.as_ref()
.and_then(|p| p.get("awaitPromise"))
.and_then(|v| v.as_bool());
let object_group = params
.as_ref()
.and_then(|p| p.get("objectGroup"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::RuntimeCallFunctionOn {
target_id: tid,
object_id,
execution_context_id,
function_declaration,
arguments,
return_by_value,
await_promise,
object_group,
},
)
} else {
Ok(serde_json::json!({ "result": { "type": "undefined" } }))
}
}
"getProperties" => {
let object_id = params
.as_ref()
.and_then(|p| p.get("objectId"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let own_properties = params
.as_ref()
.and_then(|p| p.get("ownProperties"))
.and_then(|v| v.as_bool());
if bridge.is_some() && !object_id.is_empty() {
bridge_send(
bridge,
BridgeCommand::RuntimeGetProperties {
target_id: tid,
object_id,
own_properties,
},
)
} else {
Ok(serde_json::json!({ "result": [] }))
}
}
"evaluateAsync" | "runScript" => {
Ok(serde_json::json!({ "result": { "type": "undefined" } }))
}
"releaseObject" => {
let object_id = params
.as_ref()
.and_then(|p| p.get("objectId"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if bridge.is_some() && !object_id.is_empty() {
bridge_send(
bridge,
BridgeCommand::RuntimeReleaseObject {
target_id: tid,
object_id,
},
)
} else {
ok_empty()
}
}
"releaseObjectGroup" => {
let object_group = params
.as_ref()
.and_then(|p| p.get("objectGroup"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if bridge.is_some() && !object_group.is_empty() {
bridge_send(
bridge,
BridgeCommand::RuntimeReleaseObjectGroup {
target_id: tid,
object_group,
},
)
} else {
ok_empty()
}
}
"compileScript" | "callArgument" => ok_empty(),
"runIfWaitingForDebugger" => ok_empty(),
_ => Err(CdpError {
code: -32601,
message: format!("'Runtime.{}' wasn't found", command),
}),
}
}
fn handle_dom(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" | "disable" => ok_empty(),
"getDocument" => {
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::GetDocument {
target_id: tid.clone(),
},
)
} else {
Ok(serde_json::json!({
"root": {
"nodeId": 1, "backendNodeId": 1, "nodeType": 9,
"nodeName": "#document", "localName": "", "nodeValue": "",
"childNodeCount": 1,
"children": [{
"nodeId": 2, "backendNodeId": 2, "nodeType": 1,
"nodeName": "HTML", "localName": "html", "nodeValue": "",
"childNodeCount": 2
}]
}
}))
}
}
"describeNode" => {
Ok(serde_json::json!({ "node": { "nodeId": 1, "nodeType": 1, "nodeName": "HTML" } }))
}
"querySelector" => {
let selector = params_str(params, "selector");
if bridge.is_some() && !selector.is_empty() {
bridge_send(
bridge,
BridgeCommand::QuerySelector {
target_id: tid.clone(),
selector,
},
)
} else {
Ok(serde_json::json!({ "nodeId": 0 }))
}
}
"querySelectorAll" => {
let selector = params_str(params, "selector");
if bridge.is_some() && !selector.is_empty() {
bridge_send(
bridge,
BridgeCommand::QuerySelectorAll {
target_id: tid.clone(),
selector,
},
)
} else {
Ok(serde_json::json!({ "nodeIds": [] }))
}
}
"getBoxModel" => Ok(serde_json::json!({
"model": { "width": 1920, "height": 1080, "content": [0, 0, 1920, 0, 1920, 1080, 0, 1080] }
})),
"setAttributeValue" => {
let node_id = params
.as_ref()
.and_then(|p| p.get("nodeId"))
.and_then(|v| v.as_i64())
.unwrap_or(0);
let name = params_str(params, "name");
let value = params_str(params, "value");
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::SetAttributeValue {
target_id: tid.clone(),
node_id,
name,
value,
},
)
} else {
ok_empty()
}
}
"removeAttribute" | "setOuterHTML" | "insertBefore" | "removeNode" => ok_empty(),
"getOuterHTML" => {
let node_id = params
.as_ref()
.and_then(|p| p.get("nodeId"))
.and_then(|v| v.as_i64());
bridge_send(
bridge,
BridgeCommand::GetOuterHtml {
target_id: tid,
node_id,
},
)
}
"resolveNode" => Ok(serde_json::json!({ "object": { "type": "node" } })),
"pushNodesByBackendIdsToFrontend" => Ok(serde_json::json!({ "nodeIds": [] })),
_ => Err(CdpError {
code: -32601,
message: format!("'DOM.{}' wasn't found", command),
}),
}
}
fn handle_network(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" => {
if bridge.is_some() {
bridge_send(bridge, BridgeCommand::NetworkEnable { target_id: tid })?;
}
ok_empty()
}
"disable" => {
if bridge.is_some() {
bridge_send(bridge, BridgeCommand::NetworkDisable { target_id: tid })?;
}
ok_empty()
}
"getCookies" => {
let urls: Vec<String> = params
.as_ref()
.and_then(|p| p.get("urls"))
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default();
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::GetCookies {
target_id: tid,
urls,
},
)
} else {
Ok(serde_json::json!({ "cookies": [] }))
}
}
"getAllCookies" => {
if bridge.is_some() {
bridge_send(bridge, BridgeCommand::GetAllCookies { target_id: tid })
} else {
Ok(serde_json::json!({ "cookies": [] }))
}
}
"setCookie" => {
let name = params_str(params, "name");
let value = params_str(params, "value");
let url = params
.as_ref()
.and_then(|p| p.get("url"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let domain = params
.as_ref()
.and_then(|p| p.get("domain"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::SetCookie {
target_id: tid,
name,
value,
url,
domain,
},
)
} else {
ok_empty()
}
}
"deleteCookies" => {
let name = params_str(params, "name");
let url = params
.as_ref()
.and_then(|p| p.get("url"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DeleteCookie {
target_id: tid,
name,
url,
},
)
} else {
ok_empty()
}
}
"getResponseBody" => {
let request_id = params_str(params, "requestId");
bridge_send(
bridge,
BridgeCommand::GetResponseBody {
target_id: tid,
request_id,
},
)
}
"setCacheDisabled" => {
let cache_disabled = params
.as_ref()
.and_then(|p| p.get("cacheDisabled"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::NetworkSetCacheDisabled {
target_id: tid,
cache_disabled,
},
)?;
}
ok_empty()
}
"setExtraHTTPHeaders" => {
let headers = params
.as_ref()
.and_then(|p| p.get("headers"))
.cloned()
.unwrap_or(serde_json::json!({}));
bridge_send(
bridge,
BridgeCommand::NetworkSetExtraHTTPHeaders {
target_id: tid,
headers,
},
)
}
"clearBrowserCache" => {
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::NetworkClearBrowserCache { target_id: tid },
)?;
}
ok_empty()
}
"clearBrowserCookies" => {
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::NetworkClearBrowserCookies { target_id: tid },
)?;
}
ok_empty()
}
"emulateNetworkConditions" | "setRequestInterception" | "continueInterceptedRequest" => {
ok_empty()
}
_ => Err(CdpError {
code: -32601,
message: format!("'Network.{}' wasn't found", command),
}),
}
}
fn handle_storage(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"getStorageItemsForOrigin" => {
let origin = params_str(params, "origin");
let storage_type = params_str(params, "storageType");
bridge_send(
bridge,
BridgeCommand::StorageGetStorageItemsForOrigin {
target_id: tid,
origin,
storage_type,
},
)
}
"clearDataForOrigin" => {
let origin = params_str(params, "origin");
let storage_type = params_str(params, "storageType");
bridge_send(
bridge,
BridgeCommand::StorageClearDataForOrigin {
target_id: tid,
origin,
storage_type,
},
)
}
"getCookies" => {
let urls: Vec<String> = params
.as_ref()
.and_then(|p| p.get("urls"))
.and_then(|v| v.as_array())
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default();
bridge_send(
bridge,
BridgeCommand::GetCookies {
target_id: tid,
urls,
},
)
}
_ => Err(CdpError {
code: -32601,
message: format!("'Storage.{}' wasn't found", command),
}),
}
}
fn handle_security(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" => bridge_send(bridge, BridgeCommand::SecurityEnable { target_id: tid }),
"disable" => bridge_send(bridge, BridgeCommand::SecurityDisable { target_id: tid }),
"setOverrideCertificateErrors" => {
let override_errors = params
.as_ref()
.and_then(|p| p.get("override"))
.and_then(|v| v.as_bool())
.unwrap_or(false);
bridge_send(
bridge,
BridgeCommand::SecuritySetOverrideCertificateErrors {
target_id: tid,
override_errors,
},
)
}
"handleCertificateError" | "certificateError" => ok_empty(),
_ => Err(CdpError {
code: -32601,
message: format!("'Security.{}' wasn't found", command),
}),
}
}
fn handle_css(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" | "disable" => ok_empty(),
"getComputedStyleForNode" => {
let node_id = params
.as_ref()
.and_then(|p| p.get("nodeId"))
.and_then(|v| v.as_i64())
.unwrap_or(0);
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::CssGetComputedStyleForNode {
target_id: tid,
node_id,
},
)
} else {
Ok(serde_json::json!({ "computedStyle": [] }))
}
}
"getMatchedStylesForNode" => {
let node_id = params
.as_ref()
.and_then(|p| p.get("nodeId"))
.and_then(|v| v.as_i64())
.unwrap_or(0);
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::CssGetMatchedStylesForNode {
target_id: tid,
node_id,
},
)
} else {
Ok(serde_json::json!({
"matchedCSSRules": [], "inlineStyle": null, "attributesStyle": null
}))
}
}
"getInlineStylesForNode" => {
let node_id = params
.as_ref()
.and_then(|p| p.get("nodeId"))
.and_then(|v| v.as_i64())
.unwrap_or(0);
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::CssGetInlineStylesForNode {
target_id: tid,
node_id,
},
)
} else {
Ok(serde_json::json!({ "inlineStyle": null }))
}
}
"setStyleTexts" => Ok(serde_json::json!({ "styles": [] })),
_ => Err(CdpError {
code: -32601,
message: format!("'CSS.{}' wasn't found", command),
}),
}
}
fn handle_emulation(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"setEmulatedMedia" => ok_empty(),
"setDeviceMetricsOverride" => {
let width = params
.as_ref()
.and_then(|p| p.get("width"))
.and_then(|v| v.as_u64())
.unwrap_or(1920) as u32;
let height = params
.as_ref()
.and_then(|p| p.get("height"))
.and_then(|v| v.as_u64())
.unwrap_or(1080) as u32;
let dsf = params
.as_ref()
.and_then(|p| p.get("deviceScaleFactor"))
.and_then(|v| v.as_f64());
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::SetViewport {
target_id: tid,
width,
height,
device_scale_factor: dsf,
},
)
} else {
ok_empty()
}
}
"clearDeviceMetricsOverride" => ok_empty(),
"setUserAgentOverride" => {
let ua = params_str(params, "userAgent");
if bridge.is_some() && !ua.is_empty() {
bridge_send(
bridge,
BridgeCommand::SetUserAgent {
target_id: tid,
user_agent: ua,
},
)
} else {
ok_empty()
}
}
"setTouchEmulationEnabled" | "setScriptExecutionDisabled" => ok_empty(),
"setFocusEmulationEnabled" | "setCPUThrottlingRate" => ok_empty(),
"setDefaultBackgroundColorOverride" => ok_empty(),
_ => Err(CdpError {
code: -32601,
message: format!("'Emulation.{}' wasn't found", command),
}),
}
}
fn handle_input(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"dispatchMouseEvent" => {
let event_type = params_str(params, "type");
let x = params
.as_ref()
.and_then(|p| p.get("x"))
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let y = params
.as_ref()
.and_then(|p| p.get("y"))
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let button = params
.as_ref()
.and_then(|p| p.get("button"))
.and_then(|v| v.as_i64());
let click_count = params
.as_ref()
.and_then(|p| p.get("clickCount"))
.and_then(|v| v.as_i64());
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DispatchMouseEvent {
target_id: tid.clone(),
event_type,
x,
y,
button,
click_count,
},
)
} else {
ok_empty()
}
}
"dispatchKeyEvent" => {
let event_type = params_str(params, "type");
let key = params_str(params, "key");
let code = params_str(params, "code");
let text = params
.as_ref()
.and_then(|p| p.get("text"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DispatchKeyEvent {
target_id: tid.clone(),
event_type,
key,
code,
text,
},
)
} else {
ok_empty()
}
}
"dispatchTouchEvent" => ok_empty(),
"insertText" => {
let text = params_str(params, "text");
if bridge.is_some() && !text.is_empty() {
bridge_send(
bridge,
BridgeCommand::InsertText {
target_id: tid,
text,
},
)
} else {
ok_empty()
}
}
"setIgnoreInputEvents" | "setInterceptDrags" => ok_empty(),
_ => Err(CdpError {
code: -32601,
message: format!("'Input.{}' wasn't found", command),
}),
}
}
fn handle_overlay(command: &str) -> HandlerResult {
match command {
"enable" | "disable" => ok_empty(),
"highlightNode" | "hideHighlight" | "setInspectMode" => ok_empty(),
"setPausedInDebuggerMessage" => ok_empty(),
_ => Err(CdpError {
code: -32601,
message: format!("'Overlay.{}' wasn't found", command),
}),
}
}
fn handle_debugger(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" => {
if bridge.is_some() {
bridge_send(bridge, BridgeCommand::DebuggerEnable { target_id: tid })
} else {
ok_empty()
}
}
"disable" => {
if bridge.is_some() {
bridge_send(bridge, BridgeCommand::DebuggerDisable { target_id: tid })
} else {
ok_empty()
}
}
"setBreakpointByUrl" => {
let url = params
.as_ref()
.and_then(|p| p.get("url"))
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.map(|s| s.to_string());
let url_regex = params
.as_ref()
.and_then(|p| p.get("urlRegex"))
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.map(|s| s.to_string());
let line = params
.as_ref()
.and_then(|p| p.get("lineNumber"))
.and_then(|v| v.as_u64())
.unwrap_or(0) as u32;
let column = params
.as_ref()
.and_then(|p| p.get("columnNumber"))
.and_then(|v| v.as_u64())
.map(|c| c as u32);
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DebuggerSetBreakpoint {
target_id: tid,
url,
url_regex,
line,
column,
},
)
} else {
Ok(serde_json::json!({ "breakpointId": "1", "locations": [] }))
}
}
"removeBreakpoint" => {
let breakpoint_id = params
.as_ref()
.and_then(|p| p.get("breakpointId"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DebuggerRemoveBreakpoint {
target_id: tid,
breakpoint_id,
},
)
} else {
ok_empty()
}
}
"pause" => {
if bridge.is_some() {
bridge_send(bridge, BridgeCommand::DebuggerInterrupt { target_id: tid })
} else {
ok_empty()
}
}
"resume" => {
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DebuggerResume {
target_id: tid,
step_type: None,
},
)
} else {
ok_empty()
}
}
"stepOver" => {
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DebuggerResume {
target_id: tid,
step_type: Some("next".into()),
},
)
} else {
ok_empty()
}
}
"stepInto" => {
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DebuggerResume {
target_id: tid,
step_type: Some("step".into()),
},
)
} else {
ok_empty()
}
}
"stepOut" => {
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DebuggerResume {
target_id: tid,
step_type: Some("finish".into()),
},
)
} else {
ok_empty()
}
}
"setSkipAllPauses" | "setBreakpointsActive" => ok_empty(),
"evaluateOnCallFrame" => {
let expression = params
.as_ref()
.and_then(|p| p.get("expression"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let frame_actor_id = params
.as_ref()
.and_then(|p| p.get("callFrameId"))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
if bridge.is_some() && !expression.is_empty() {
bridge_send(
bridge,
BridgeCommand::DebuggerEval {
target_id: tid,
expression,
frame_actor_id,
},
)
} else {
Ok(serde_json::json!({ "result": { "type": "undefined" } }))
}
}
"getPossibleBreakpoints" => {
let start_script_id = params
.as_ref()
.and_then(|p| p.get("start"))
.and_then(|v| v.get("scriptId"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DebuggerGetPossibleBreakpoints {
target_id: tid,
start_script_id,
},
)
} else {
Ok(serde_json::json!({ "locations": [] }))
}
}
"getScriptSource" => {
let script_id = params
.as_ref()
.and_then(|p| p.get("scriptId"))
.and_then(|v| v.as_str())
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(0);
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::DebuggerGetScriptSource {
target_id: tid,
script_id,
},
)
} else {
Ok(serde_json::json!({ "scriptSource": "" }))
}
}
"setPauseOnExceptions" => ok_empty(),
_ => Err(CdpError {
code: -32601,
message: format!("'Debugger.{}' wasn't found", command),
}),
}
}
fn handle_log(command: &str) -> HandlerResult {
match command {
"enable" | "disable" | "clear" => ok_empty(),
"startViolationsReport" | "stopViolationsReport" => ok_empty(),
_ => Err(CdpError {
code: -32601,
message: format!("'Log.{}' wasn't found", command),
}),
}
}
fn handle_fetch(command: &str, _params: &Option<Value>) -> HandlerResult {
match command {
"disable" => ok_empty(),
"enable"
| "continueRequest"
| "continueWithResponse"
| "failRequest"
| "fulfillRequest"
| "getRequestPostData"
| "continueWithAuth"
| "takeResponseBodyAsStream" => Err(not_supported(
&format!("Fetch.{command}"),
"bao has no request interception facility: the servo embedder does not expose request pausing, so requests cannot be paused, continued, fulfilled or failed",
)),
_ => Err(CdpError {
code: -32601,
message: format!("'Fetch.{}' wasn't found", command),
}),
}
}
fn handle_profiler(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" | "disable" => ok_empty(),
"start" => bridge_send(bridge, BridgeCommand::ProfilerStart { target_id: tid }),
"stop" => bridge_send(bridge, BridgeCommand::ProfilerStop { target_id: tid }),
"setSamplingInterval" => {
let interval = params
.as_ref()
.and_then(|p| p.get("interval"))
.and_then(|v| v.as_u64())
.unwrap_or(1000) as u32;
bridge_send(
bridge,
BridgeCommand::ProfilerSetSamplingInterval {
target_id: tid,
interval,
},
)
}
_ => Err(CdpError {
code: -32601,
message: format!("'Profiler.{}' wasn't found", command),
}),
}
}
fn handle_heap_profiler(
command: &str,
target_id: &str,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" | "disable" => ok_empty(),
"takeHeapSnapshot" => {
bridge_send(
bridge,
BridgeCommand::HeapProfilerTakeSnapshot { target_id: tid },
)
}
"startTrackingHeapObjects" => {
bridge_send(
bridge,
BridgeCommand::HeapProfilerStartTracking { target_id: tid },
)
}
"stopTrackingHeapObjects" => {
bridge_send(
bridge,
BridgeCommand::HeapProfilerStopTracking { target_id: tid },
)
}
"collectGarbage" => {
bridge_send(
bridge,
BridgeCommand::HeapProfilerCollectGarbage { target_id: tid },
)
}
_ => Err(CdpError {
code: -32601,
message: format!("'HeapProfiler.{}' wasn't found", command),
}),
}
}
fn handle_memory(command: &str, target_id: &str, bridge: Option<&BridgeSender>) -> HandlerResult {
let tid = target_id.to_string();
match command {
"getDOMCounters" => {
bridge_send(
bridge,
BridgeCommand::MemoryGetDOMCounters { target_id: tid },
)
}
"prepareForLeakDetection" => ok_empty(),
"forciblyPurgeJavaScriptMemory" => {
bridge_send(bridge, BridgeCommand::MemoryPurgeJS { target_id: tid })
}
_ => Err(CdpError {
code: -32601,
message: format!("'Memory.{}' wasn't found", command),
}),
}
}
fn handle_performance(
command: &str,
target_id: &str,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" | "disable" => ok_empty(),
"getMetrics" => {
bridge_send(
bridge,
BridgeCommand::PerformanceGetMetrics { target_id: tid },
)
}
_ => Err(CdpError {
code: -32601,
message: format!("'Performance.{}' wasn't found", command),
}),
}
}
fn handle_system_info(command: &str) -> HandlerResult {
match command {
"getInfo" => {
let os_name = std::env::consts::OS;
let arch = std::env::consts::ARCH;
let pid = std::process::id();
Ok(serde_json::json!({
"gpu": {
"vendorString": "Bao",
"deviceString": "Software Renderer"
},
"modelName": "Bao",
"modelVersion": env!("CARGO_PKG_VERSION"),
"commandLine": "",
"platform": os_name,
"product": "Bao",
"cpu": {
"arch": arch,
"processors": num_cpus()
},
"osName": os_name,
"osVersion": "",
"pid": pid
}))
}
"getProcessInfo" => {
let pid = std::process::id();
Ok(serde_json::json!({
"processInfo": [{
"id": pid,
"type": "browser",
"name": "Bao"
}]
}))
}
_ => Err(CdpError {
code: -32601,
message: format!("'SystemInfo.{}' wasn't found", command),
}),
}
}
fn num_cpus() -> u32 {
std::thread::available_parallelism()
.map(|n| n.get() as u32)
.unwrap_or(1)
}
fn handle_browser(command: &str) -> HandlerResult {
match command {
"getVersion" => Ok(serde_json::json!({
"protocolVersion": "1.3",
"product": "Bao/0.1.0",
"revision": env!("CARGO_PKG_VERSION"),
"userAgent": format!("Bao/{}", env!("CARGO_PKG_VERSION")),
"jsVersion": "SpiderMonkey",
})),
"setDownloadBehavior" => ok_empty(),
_ => Err(CdpError {
code: -32601,
message: format!("'Browser.{}' wasn't found", command),
}),
}
}
fn handle_service_worker(
command: &str,
target_id: &str,
params: &Option<Value>,
bridge: Option<&BridgeSender>,
) -> HandlerResult {
let tid = target_id.to_string();
match command {
"enable" | "disable" => ok_empty(),
"deliverPushMessage" => {
let origin = params_str(params, "origin");
let registration_id = params_str(params, "registrationId");
Ok(serde_json::json!({
"origin": origin,
"registrationId": registration_id,
"delivered": true
}))
}
"dispatchPeriodicSyncEvent" | "dispatchSyncEvent" => ok_empty(),
"getAllRegistrations" => {
if bridge.is_some() {
bridge_send(
bridge,
BridgeCommand::ListServiceWorkerRegistrations { target_id: tid },
)
} else {
Ok(serde_json::json!({ "registrations": [] }))
}
}
"getRegistration" => {
let registration_id = params_str(params, "registrationId");
if bridge.is_some() && !registration_id.is_empty() {
bridge_send(
bridge,
BridgeCommand::GetServiceWorkerRegistrationInfo {
target_id: tid,
registration_id,
},
)
} else {
Ok(serde_json::json!({ "registration": null }))
}
}
"stopWorker" => {
let registration_id = params_str(params, "registrationId");
if bridge.is_some() && !registration_id.is_empty() {
bridge_send(
bridge,
BridgeCommand::StopServiceWorker {
target_id: tid,
registration_id,
},
)?;
}
ok_empty()
}
"unregister" => {
let registration_id = params_str(params, "registrationId");
if bridge.is_some() && !registration_id.is_empty() {
bridge_send(
bridge,
BridgeCommand::TerminateServiceWorker {
target_id: tid,
registration_id,
},
)?;
}
ok_empty()
}
"updateRegistration" => ok_empty(),
_ => Err(CdpError {
code: -32601,
message: format!("'ServiceWorker.{}' wasn't found", command),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn parse_message_valid_json() {
let msg = parse_message(r#"{"id":1,"method":"Page.enable","params":{"url":"http://x"}}"#)
.unwrap();
assert_eq!(msg.id, Some(1));
assert_eq!(msg.method, "Page.enable");
assert_eq!(msg.params, Some(json!({"url": "http://x"})));
assert_eq!(msg.session_id, None);
}
#[test]
fn parse_message_invalid_json() {
assert!(parse_message("{not json}").is_none());
}
#[test]
fn parse_message_missing_method() {
assert!(parse_message(r#"{"id":1}"#).is_none());
}
#[test]
fn parse_message_with_session_id() {
let raw = r#"{"id":5,"method":"Runtime.evaluate","sessionId":"abc123"}"#;
let msg = parse_message(raw).expect("should parse valid JSON with session_id");
assert_eq!(msg.id, Some(5));
assert_eq!(msg.method, "Runtime.evaluate");
assert_eq!(msg.session_id, Some("abc123".to_string()));
}
#[test]
fn parse_message_null_params() {
let msg = parse_message(r#"{"id":2,"method":"Page.enable","params":null}"#).unwrap();
assert_eq!(msg.id, Some(2));
assert_eq!(msg.params, None);
}
#[test]
fn serialize_response_with_result() {
let resp = CdpResponse {
id: Some(1),
result: Some(json!({"key": "val"})),
error: None,
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["id"], 1);
assert_eq!(parsed["result"]["key"], "val");
assert!(parsed.get("error").is_none());
}
#[test]
fn serialize_response_with_error() {
let resp = CdpResponse {
id: Some(2),
result: None,
error: Some(CdpError {
code: -32601,
message: "not found".into(),
}),
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["id"], 2);
assert!(parsed.get("result").is_none());
assert_eq!(parsed["error"]["code"], -32601);
assert_eq!(parsed["error"]["message"], "not found");
}
#[test]
fn handle_command_unknown_domain() {
let msg = CdpMessage {
id: Some(1),
method: "Foo.bar".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32601);
}
#[test]
fn handle_command_target_get_targets() {
let msg = CdpMessage {
id: Some(2),
method: "Target.getTargets".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert!(result.get("targetInfos").unwrap().as_array().unwrap().len() > 0);
assert_eq!(result["targetInfos"][0]["targetId"], "t1");
}
#[test]
fn handle_command_target_create_target() {
let msg = CdpMessage {
id: Some(3),
method: "Target.createTarget".into(),
params: Some(json!({"url": "https://example.com"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge connected"));
}
#[test]
fn handle_command_target_close_target() {
let msg = CdpMessage {
id: Some(4),
method: "Target.closeTarget".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge connected"));
}
#[test]
fn handle_command_target_set_auto_attach() {
let msg = CdpMessage {
id: Some(5),
method: "Target.setAutoAttach".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_page_enable() {
let msg = CdpMessage {
id: Some(6),
method: "Page.enable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_page_get_layout_metrics() {
let msg = CdpMessage {
id: Some(7),
method: "Page.getLayoutMetrics".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge"));
}
#[test]
fn handle_command_runtime_enable() {
let msg = CdpMessage {
id: Some(8),
method: "Runtime.enable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_runtime_evaluate_no_bridge() {
let msg = CdpMessage {
id: Some(9),
method: "Runtime.evaluate".into(),
params: Some(json!({"expression": ""})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["result"]["type"], "undefined");
}
#[test]
fn handle_command_dom_get_document() {
let msg = CdpMessage {
id: Some(10),
method: "DOM.getDocument".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
let root = result.get("root").unwrap();
assert_eq!(root["nodeId"], 1);
assert_eq!(root["nodeType"], 9);
assert_eq!(root["nodeName"], "#document");
}
#[test]
fn handle_command_dom_query_selector() {
let msg = CdpMessage {
id: Some(11),
method: "DOM.querySelector".into(),
params: Some(json!({"selector": "div"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["nodeId"], 0);
}
#[test]
fn handle_command_network_enable() {
let msg = CdpMessage {
id: Some(12),
method: "Network.enable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_network_get_cookies() {
let msg = CdpMessage {
id: Some(13),
method: "Network.getCookies".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["cookies"], json!([]));
}
#[test]
fn handle_command_css_enable() {
let msg = CdpMessage {
id: Some(14),
method: "CSS.enable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_css_get_computed_style() {
let msg = CdpMessage {
id: Some(15),
method: "CSS.getComputedStyleForNode".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["computedStyle"], json!([]));
}
#[test]
fn handle_command_emulation_set_device_metrics() {
let msg = CdpMessage {
id: Some(16),
method: "Emulation.setDeviceMetricsOverride".into(),
params: Some(json!({"width": 800, "height": 600, "deviceScaleFactor": 2})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_input_dispatch_mouse() {
let msg = CdpMessage {
id: Some(17),
method: "Input.dispatchMouseEvent".into(),
params: Some(
json!({"type": "mousePressed", "x": 100, "y": 200, "button": 0, "clickCount": 1}),
),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_overlay_enable() {
let msg = CdpMessage {
id: Some(18),
method: "Overlay.enable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_debugger_enable() {
let msg = CdpMessage {
id: Some(19),
method: "Debugger.enable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_debugger_set_breakpoint_by_url() {
let msg = CdpMessage {
id: Some(20),
method: "Debugger.setBreakpointByUrl".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["breakpointId"], "1");
}
#[test]
fn handle_command_log_enable() {
let msg = CdpMessage {
id: Some(21),
method: "Log.enable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_fetch_enable_with_patterns() {
let msg = CdpMessage {
id: Some(22),
method: "Fetch.enable".into(),
params: Some(json!({"patterns": [{"urlPattern": "*"}]})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn handle_command_fetch_continue_request() {
let msg = CdpMessage {
id: Some(23),
method: "Fetch.continueRequest".into(),
params: Some(json!({"requestId": "req-001"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn cdp_error_clone_and_debug() {
let err = CdpError {
code: -32601,
message: "not found".into(),
};
let cloned = err.clone();
assert_eq!(cloned.code, err.code);
assert_eq!(cloned.message, err.message);
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("-32601"));
assert!(debug_str.contains("not found"));
}
#[test]
fn cdp_event_serialize() {
let ev = CdpEvent {
method: "Page.loadEventFired".into(),
params: Some(json!({"timestamp": 12345})),
};
let s = serialize_event(&ev);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["method"], "Page.loadEventFired");
assert_eq!(parsed["params"]["timestamp"], 12345);
}
#[test]
fn parse_message_unicode_method() {
let msg = parse_message(r#"{"id":99,"method":"Page.你好世界"}"#).unwrap();
assert_eq!(msg.id, Some(99));
assert_eq!(msg.method, "Page.你好世界");
}
#[test]
fn parse_message_id_zero() {
let msg = parse_message(r#"{"id":0,"method":"Page.enable"}"#).unwrap();
assert_eq!(msg.id, Some(0));
assert_eq!(msg.method, "Page.enable");
}
#[test]
fn parse_message_id_max() {
let msg = parse_message(r#"{"id":9223372036854775807,"method":"Page.enable"}"#).unwrap();
assert_eq!(msg.id, Some(i64::MAX));
}
#[test]
fn parse_message_negative_id() {
let msg = parse_message(r#"{"id":-1,"method":"Page.enable"}"#).unwrap();
assert_eq!(msg.id, Some(-1));
}
#[test]
fn parse_message_id_min() {
let msg = parse_message(r#"{"id":-9223372036854775808,"method":"Page.enable"}"#).unwrap();
assert_eq!(msg.id, Some(i64::MIN));
}
#[test]
fn parse_message_empty_method() {
let msg = parse_message(r#"{"id":1,"method":""}"#).unwrap();
assert_eq!(msg.method, "");
}
#[test]
fn parse_message_method_no_dot() {
let msg = parse_message(r#"{"id":1,"method":"NoDomain"}"#).unwrap();
assert_eq!(msg.method, "NoDomain");
}
#[test]
fn parse_message_method_multiple_dots() {
let msg = parse_message(r#"{"id":1,"method":"Page.navigate.to"}"#).unwrap();
assert_eq!(msg.method, "Page.navigate.to");
let parts: Vec<&str> = msg.method.splitn(2, '.').collect();
assert_eq!(parts[0], "Page");
assert_eq!(parts[1], "navigate.to");
}
#[test]
fn parse_message_empty_string() {
assert!(parse_message("").is_none());
}
#[test]
fn parse_message_whitespace_only() {
assert!(parse_message(" ").is_none());
}
#[test]
fn parse_message_extra_fields() {
let msg = parse_message(r#"{"id":1,"method":"Page.enable","extra":"ignored"}"#);
assert!(msg.is_some());
assert_eq!(msg.unwrap().method, "Page.enable");
}
#[test]
fn parse_message_params_object() {
let msg =
parse_message(r#"{"id":1,"method":"Page.navigate","params":{"url":"http://x.com"}}"#)
.unwrap();
assert!(msg.params.is_some());
assert_eq!(msg.params.unwrap()["url"], "http://x.com");
}
#[test]
fn parse_message_params_array() {
let msg = parse_message(r#"{"id":1,"method":"Test.cmd","params":[1,2,3]}"#).unwrap();
assert!(msg.params.is_some());
assert!(msg.params.unwrap().is_array());
}
#[test]
fn parse_message_params_string() {
let msg = parse_message(r#"{"id":1,"method":"Test.cmd","params":"hello"}"#).unwrap();
assert!(msg.params.is_some());
assert!(msg.params.unwrap().is_string());
}
#[test]
fn parse_message_params_number() {
let msg = parse_message(r#"{"id":1,"method":"Test.cmd","params":42}"#).unwrap();
assert!(msg.params.is_some());
assert!(msg.params.unwrap().is_number());
}
#[test]
fn parse_message_params_boolean() {
let msg = parse_message(r#"{"id":1,"method":"Test.cmd","params":true}"#).unwrap();
assert!(msg.params.is_some());
assert!(msg.params.unwrap().is_boolean());
}
#[test]
fn parse_message_long_session_id() {
let long_session = "A".repeat(10000);
let raw = format!(
r#"{{"id":1,"method":"Page.enable","sessionId":"{}"}}"#,
long_session
);
let msg = parse_message(&raw).unwrap();
assert_eq!(msg.session_id.unwrap().len(), 10000);
}
#[test]
fn parse_message_empty_session_id() {
let msg = parse_message(r#"{"id":1,"method":"Page.enable","sessionId":""}"#).unwrap();
assert_eq!(msg.session_id, Some("".to_string()));
}
#[test]
fn serialize_response_null_result() {
let resp = CdpResponse {
id: Some(1),
result: Some(Value::Null),
error: None,
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["id"], 1);
assert_eq!(parsed["result"], Value::Null);
}
#[test]
fn serialize_response_empty_object_result() {
let resp = CdpResponse {
id: Some(2),
result: Some(json!({})),
error: None,
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["result"], json!({}));
}
#[test]
fn serialize_response_nested_result() {
let resp = CdpResponse {
id: Some(3),
result: Some(json!({"root": {"nodeId": 1, "children": [{"nodeId": 2}]}})),
error: None,
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["result"]["root"]["nodeId"], 1);
assert_eq!(parsed["result"]["root"]["children"][0]["nodeId"], 2);
}
#[test]
fn serialize_response_id_zero() {
let resp = CdpResponse {
id: Some(0),
result: Some(json!({"ok": true})),
error: None,
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["id"], 0);
}
#[test]
fn serialize_response_negative_id() {
let resp = CdpResponse {
id: Some(-42),
result: Some(json!({})),
error: None,
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["id"], -42);
}
#[test]
fn serialize_response_max_id() {
let resp = CdpResponse {
id: Some(i64::MAX),
result: Some(json!({})),
error: None,
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["id"], i64::MAX);
}
#[test]
fn serialize_response_array_result() {
let resp = CdpResponse {
id: Some(5),
result: Some(json!([1, 2, 3])),
error: None,
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["result"], json!([1, 2, 3]));
}
#[test]
fn serialize_response_string_result() {
let resp = CdpResponse {
id: Some(6),
result: Some(json!("hello world")),
error: None,
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["result"], "hello world");
}
#[test]
fn cdp_error_code_parse_error() {
let resp = CdpResponse {
id: Some(1),
result: None,
error: Some(CdpError {
code: -32700,
message: "Parse error".into(),
}),
};
let s = serialize_response(&resp);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["error"]["code"], -32700);
assert_eq!(parsed["error"]["message"], "Parse error");
}
#[test]
fn cdp_error_code_invalid_request() {
let err = CdpError {
code: -32600,
message: "Invalid Request".into(),
};
assert_eq!(err.code, -32600);
let s = serde_json::to_string(&err).unwrap();
assert!(s.contains("-32600"));
}
#[test]
fn cdp_error_code_method_not_found() {
let err = CdpError {
code: -32601,
message: "Method not found".into(),
};
assert_eq!(err.code, -32601);
}
#[test]
fn cdp_error_code_invalid_params() {
let err = CdpError {
code: -32602,
message: "Invalid params".into(),
};
assert_eq!(err.code, -32602);
}
#[test]
fn cdp_error_code_internal_error() {
let err = CdpError {
code: -32603,
message: "Internal error".into(),
};
assert_eq!(err.code, -32603);
}
#[test]
fn cdp_error_custom_code() {
let err = CdpError {
code: -32000,
message: "Server error".into(),
};
let s = serde_json::to_string(&err).unwrap();
assert!(s.contains("-32000"));
assert!(s.contains("Server error"));
}
#[test]
fn cdp_error_empty_message() {
let err = CdpError {
code: -32601,
message: String::new(),
};
let s = serde_json::to_string(&err).unwrap();
assert!(s.contains("-32601"));
}
#[test]
fn cdp_error_unicode_message() {
let err = CdpError {
code: -32601,
message: "方法未找到".into(),
};
let s = serde_json::to_string(&err).unwrap();
assert!(s.contains("方法未找到"));
}
#[test]
fn cdp_event_no_params() {
let ev = CdpEvent {
method: "Page.domContentEventFired".into(),
params: None,
};
let s = serialize_event(&ev);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["method"], "Page.domContentEventFired");
assert!(
parsed.get("params").is_none(),
"params should be skipped when None"
);
}
#[test]
fn cdp_event_large_data() {
let large_string = "X".repeat(100_000);
let ev = CdpEvent {
method: "Network.dataReceived".into(),
params: Some(
json!({ "dataLength": large_string.len(), "encodedDataLength": large_string.len() }),
),
};
let s = serialize_event(&ev);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["params"]["dataLength"], 100_000);
}
#[test]
fn cdp_event_nested_params() {
let ev = CdpEvent {
method: "DOM.attributeModified".into(),
params: Some(json!({
"nodeId": 1,
"name": "class",
"value": "container active",
"metadata": {
"source": "user",
"timestamp": 1234567890
}
})),
};
let s = serialize_event(&ev);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["params"]["nodeId"], 1);
assert_eq!(parsed["params"]["metadata"]["source"], "user");
}
#[test]
fn cdp_event_null_params() {
let ev = CdpEvent {
method: "Page.frameResized".into(),
params: Some(Value::Null),
};
let s = serialize_event(&ev);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["params"], Value::Null);
}
#[test]
fn cdp_event_empty_method() {
let ev = CdpEvent {
method: String::new(),
params: None,
};
let s = serialize_event(&ev);
let parsed: Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["method"], "");
}
#[test]
fn handle_command_no_dot_method() {
let msg = CdpMessage {
id: Some(1),
method: "NoDomain".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32601);
assert!(err.message.contains("NoDomain"));
}
#[test]
fn handle_command_empty_method() {
let msg = CdpMessage {
id: Some(2),
method: String::new(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32601);
}
#[test]
fn handle_command_known_domain_unknown_command() {
let msg = CdpMessage {
id: Some(3),
method: "Page.nonExistentCommand".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32601);
assert!(err.message.contains("Page.nonExistentCommand"));
}
#[test]
fn handle_command_target_get_target_info() {
let msg = CdpMessage {
id: Some(4),
method: "Target.getTargetInfo".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
let info = result.get("targetInfo").unwrap();
assert_eq!(info["targetId"], "t1");
assert_eq!(info["type"], "page");
assert_eq!(info["attached"], true);
}
#[test]
fn handle_command_target_attach_to_target() {
let msg = CdpMessage {
id: Some(5),
method: "Target.attachToTarget".into(),
params: Some(json!({"targetId": "t1", "flatten": true})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("WS session registry"));
}
#[test]
fn handle_command_target_detach_from_target() {
let msg = CdpMessage {
id: Some(6),
method: "Target.detachFromTarget".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("WS session registry"));
}
#[test]
fn handle_command_target_set_discover_targets() {
let msg = CdpMessage {
id: Some(7),
method: "Target.setDiscoverTargets".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn handle_command_target_get_target_targets() {
let msg = CdpMessage {
id: Some(8),
method: "Target.getTargetTargets".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert!(result.get("targetInfos").unwrap().as_array().unwrap().len() > 0);
}
#[test]
fn handle_command_page_navigate_no_bridge_default_url() {
let msg = CdpMessage {
id: Some(9),
method: "Page.navigate".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge"));
}
#[test]
fn handle_command_page_navigate_with_url() {
let msg = CdpMessage {
id: Some(10),
method: "Page.navigate".into(),
params: Some(json!({"url": "https://example.com"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge"));
}
#[test]
fn handle_command_page_reload_no_bridge() {
let msg = CdpMessage {
id: Some(11),
method: "Page.reload".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge"));
}
#[test]
fn handle_command_page_get_frame_tree() {
let msg = CdpMessage {
id: Some(12),
method: "Page.getFrameTree".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge"));
}
#[test]
fn handle_command_page_get_navigation_history() {
let msg = CdpMessage {
id: Some(13),
method: "Page.getNavigationHistory".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("history enumeration must fail loudly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("not supported"));
}
#[test]
fn handle_command_page_capture_screenshot_no_bridge() {
let msg = CdpMessage {
id: Some(14),
method: "Page.captureScreenshot".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge"));
}
#[test]
fn handle_command_page_add_script_empty_source() {
let msg = CdpMessage {
id: Some(15),
method: "Page.addScriptToEvaluateOnNewDocument".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let result = resp.result.expect("empty source registers as a no-op");
assert!(result["identifier"].as_str().unwrap().starts_with("script-"));
}
#[test]
fn handle_command_page_remove_script() {
let msg = CdpMessage {
id: Some(16),
method: "Page.removeScriptToEvaluateOnNewDocument".into(),
params: Some(json!({"identifier": "script-1"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("removal must fail loudly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("not supported"));
}
#[test]
fn handle_command_page_set_content() {
let msg = CdpMessage {
id: Some(17),
method: "Page.setContent".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("missing html must be rejected");
assert_eq!(err.code, -32602);
assert!(err.message.contains("html"));
}
#[test]
fn handle_command_page_close() {
let msg = CdpMessage {
id: Some(18),
method: "Page.close".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
}
#[test]
fn handle_command_page_bring_to_front() {
let msg = CdpMessage {
id: Some(19),
method: "Page.bringToFront".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
}
#[test]
fn handle_command_page_disable() {
let msg = CdpMessage {
id: Some(20),
method: "Page.disable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_runtime_disable() {
let msg = CdpMessage {
id: Some(21),
method: "Runtime.disable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_runtime_call_function_on() {
let msg = CdpMessage {
id: Some(22),
method: "Runtime.callFunctionOn".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["result"]["type"], "undefined");
}
#[test]
fn handle_command_runtime_get_properties() {
let msg = CdpMessage {
id: Some(23),
method: "Runtime.getProperties".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["result"], json!([]));
}
#[test]
fn handle_command_runtime_evaluate_async() {
let msg = CdpMessage {
id: Some(24),
method: "Runtime.evaluateAsync".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_runtime_run_script() {
let msg = CdpMessage {
id: Some(25),
method: "Runtime.runScript".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_runtime_release_object() {
let msg = CdpMessage {
id: Some(26),
method: "Runtime.releaseObject".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_runtime_release_object_group() {
let msg = CdpMessage {
id: Some(27),
method: "Runtime.releaseObjectGroup".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_runtime_compile_script() {
let msg = CdpMessage {
id: Some(28),
method: "Runtime.compileScript".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_runtime_unknown_command() {
let msg = CdpMessage {
id: Some(29),
method: "Runtime.unknownMethod".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32601);
assert!(err.message.contains("Runtime.unknownMethod"));
}
#[test]
fn handle_command_dom_enable() {
let msg = CdpMessage {
id: Some(30),
method: "DOM.enable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_dom_disable() {
let msg = CdpMessage {
id: Some(31),
method: "DOM.disable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_dom_describe_node() {
let msg = CdpMessage {
id: Some(32),
method: "DOM.describeNode".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert!(result.get("node").is_some());
}
#[test]
fn handle_command_dom_get_box_model() {
let msg = CdpMessage {
id: Some(33),
method: "DOM.getBoxModel".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert!(result.get("model").is_some());
assert_eq!(result["model"]["width"], 1920);
assert_eq!(result["model"]["height"], 1080);
}
#[test]
fn handle_command_dom_set_attribute_value_no_bridge() {
let msg = CdpMessage {
id: Some(34),
method: "DOM.setAttributeValue".into(),
params: Some(json!({"nodeId": 1, "name": "class", "value": "active"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_dom_remove_attribute() {
let msg = CdpMessage {
id: Some(35),
method: "DOM.removeAttribute".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_dom_set_outer_html() {
let msg = CdpMessage {
id: Some(36),
method: "DOM.setOuterHTML".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_dom_insert_before() {
let msg = CdpMessage {
id: Some(37),
method: "DOM.insertBefore".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_dom_remove_node() {
let msg = CdpMessage {
id: Some(38),
method: "DOM.removeNode".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_dom_get_outer_html_no_bridge() {
let msg = CdpMessage {
id: Some(39),
method: "DOM.getOuterHTML".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
}
#[test]
fn handle_command_dom_resolve_node() {
let msg = CdpMessage {
id: Some(40),
method: "DOM.resolveNode".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["object"]["type"], "node");
}
#[test]
fn handle_command_dom_push_nodes_by_backend_ids() {
let msg = CdpMessage {
id: Some(41),
method: "DOM.pushNodesByBackendIdsToFrontend".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["nodeIds"], json!([]));
}
#[test]
fn handle_command_dom_unknown_command() {
let msg = CdpMessage {
id: Some(42),
method: "DOM.nonExistent".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
assert_eq!(resp.error.unwrap().code, -32601);
}
#[test]
fn handle_command_network_disable() {
let msg = CdpMessage {
id: Some(43),
method: "Network.disable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_network_get_response_body() {
let msg = CdpMessage {
id: Some(44),
method: "Network.getResponseBody".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
}
#[test]
fn handle_command_network_set_cache_disabled() {
let msg = CdpMessage {
id: Some(45),
method: "Network.setCacheDisabled".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_network_set_extra_http_headers() {
let msg = CdpMessage {
id: Some(46),
method: "Network.setExtraHTTPHeaders".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
let err = resp.error.expect("no bridge must yield an error");
assert_eq!(err.code, -32603);
}
#[test]
fn handle_command_network_emulate_conditions() {
let msg = CdpMessage {
id: Some(47),
method: "Network.emulateNetworkConditions".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_network_get_all_cookies() {
let msg = CdpMessage {
id: Some(48),
method: "Network.getAllCookies".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["cookies"], json!([]));
}
#[test]
fn handle_command_network_delete_cookies() {
let msg = CdpMessage {
id: Some(49),
method: "Network.deleteCookies".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_network_set_cookie() {
let msg = CdpMessage {
id: Some(50),
method: "Network.setCookie".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_network_set_request_interception() {
let msg = CdpMessage {
id: Some(51),
method: "Network.setRequestInterception".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_network_continue_intercepted_request() {
let msg = CdpMessage {
id: Some(52),
method: "Network.continueInterceptedRequest".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_network_unknown() {
let msg = CdpMessage {
id: Some(53),
method: "Network.bogus".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
assert_eq!(resp.error.unwrap().code, -32601);
}
#[test]
fn handle_command_css_disable() {
let msg = CdpMessage {
id: Some(54),
method: "CSS.disable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_css_get_matched_styles() {
let msg = CdpMessage {
id: Some(55),
method: "CSS.getMatchedStylesForNode".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["matchedCSSRules"], json!([]));
assert_eq!(result["inlineStyle"], Value::Null);
}
#[test]
fn handle_command_css_get_inline_styles() {
let msg = CdpMessage {
id: Some(56),
method: "CSS.getInlineStylesForNode".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["inlineStyle"], Value::Null);
}
#[test]
fn handle_command_css_set_style_texts() {
let msg = CdpMessage {
id: Some(57),
method: "CSS.setStyleTexts".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["styles"], json!([]));
}
#[test]
fn handle_command_css_unknown() {
let msg = CdpMessage {
id: Some(58),
method: "CSS.bogus".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
assert_eq!(resp.error.unwrap().code, -32601);
}
#[test]
fn handle_command_emulation_clear_device_metrics() {
let msg = CdpMessage {
id: Some(59),
method: "Emulation.clearDeviceMetricsOverride".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_emulation_set_user_agent_no_bridge() {
let msg = CdpMessage {
id: Some(60),
method: "Emulation.setUserAgentOverride".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_emulation_set_touch_emulation() {
let msg = CdpMessage {
id: Some(61),
method: "Emulation.setTouchEmulationEnabled".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_emulation_set_script_execution_disabled() {
let msg = CdpMessage {
id: Some(62),
method: "Emulation.setScriptExecutionDisabled".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_emulation_set_focus_emulation() {
let msg = CdpMessage {
id: Some(63),
method: "Emulation.setFocusEmulationEnabled".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_emulation_set_cpu_throttling() {
let msg = CdpMessage {
id: Some(64),
method: "Emulation.setCPUThrottlingRate".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_emulation_set_default_bg_color() {
let msg = CdpMessage {
id: Some(65),
method: "Emulation.setDefaultBackgroundColorOverride".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_emulation_unknown() {
let msg = CdpMessage {
id: Some(66),
method: "Emulation.bogus".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
assert_eq!(resp.error.unwrap().code, -32601);
}
#[test]
fn handle_command_input_dispatch_key_no_bridge() {
let msg = CdpMessage {
id: Some(67),
method: "Input.dispatchKeyEvent".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_input_dispatch_touch() {
let msg = CdpMessage {
id: Some(68),
method: "Input.dispatchTouchEvent".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_input_insert_text_no_bridge() {
let msg = CdpMessage {
id: Some(69),
method: "Input.insertText".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_input_set_ignore_input_events() {
let msg = CdpMessage {
id: Some(70),
method: "Input.setIgnoreInputEvents".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_input_set_intercept_drags() {
let msg = CdpMessage {
id: Some(71),
method: "Input.setInterceptDrags".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_input_unknown() {
let msg = CdpMessage {
id: Some(72),
method: "Input.bogus".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
assert_eq!(resp.error.unwrap().code, -32601);
}
#[test]
fn handle_command_overlay_highlight_node() {
let msg = CdpMessage {
id: Some(73),
method: "Overlay.highlightNode".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_overlay_hide_highlight() {
let msg = CdpMessage {
id: Some(74),
method: "Overlay.hideHighlight".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_overlay_set_inspect_mode() {
let msg = CdpMessage {
id: Some(75),
method: "Overlay.setInspectMode".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_overlay_set_paused_in_debugger() {
let msg = CdpMessage {
id: Some(76),
method: "Overlay.setPausedInDebuggerMessage".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_overlay_unknown() {
let msg = CdpMessage {
id: Some(77),
method: "Overlay.bogus".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
assert_eq!(resp.error.unwrap().code, -32601);
}
#[test]
fn handle_command_debugger_disable() {
let msg = CdpMessage {
id: Some(78),
method: "Debugger.disable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_debugger_remove_breakpoint() {
let msg = CdpMessage {
id: Some(79),
method: "Debugger.removeBreakpoint".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_debugger_pause() {
let msg = CdpMessage {
id: Some(80),
method: "Debugger.pause".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_debugger_resume() {
let msg = CdpMessage {
id: Some(81),
method: "Debugger.resume".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_debugger_step_over() {
let msg = CdpMessage {
id: Some(82),
method: "Debugger.stepOver".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_debugger_step_into() {
let msg = CdpMessage {
id: Some(83),
method: "Debugger.stepInto".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_debugger_step_out() {
let msg = CdpMessage {
id: Some(84),
method: "Debugger.stepOut".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_debugger_set_skip_all_pauses() {
let msg = CdpMessage {
id: Some(85),
method: "Debugger.setSkipAllPauses".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_debugger_set_breakpoints_active() {
let msg = CdpMessage {
id: Some(86),
method: "Debugger.setBreakpointsActive".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_debugger_evaluate_on_call_frame() {
let msg = CdpMessage {
id: Some(87),
method: "Debugger.evaluateOnCallFrame".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["result"]["type"], "undefined");
}
#[test]
fn handle_command_debugger_get_possible_breakpoints() {
let msg = CdpMessage {
id: Some(88),
method: "Debugger.getPossibleBreakpoints".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["locations"], json!([]));
}
#[test]
fn handle_command_debugger_get_script_source() {
let msg = CdpMessage {
id: Some(89),
method: "Debugger.getScriptSource".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["scriptSource"], "");
}
#[test]
fn handle_command_debugger_set_pause_on_exceptions() {
let msg = CdpMessage {
id: Some(90),
method: "Debugger.setPauseOnExceptions".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_debugger_unknown() {
let msg = CdpMessage {
id: Some(91),
method: "Debugger.bogus".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
assert_eq!(resp.error.unwrap().code, -32601);
}
#[test]
fn handle_command_log_disable() {
let msg = CdpMessage {
id: Some(92),
method: "Log.disable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_log_clear() {
let msg = CdpMessage {
id: Some(93),
method: "Log.clear".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_log_start_violations_report() {
let msg = CdpMessage {
id: Some(94),
method: "Log.startViolationsReport".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_log_stop_violations_report() {
let msg = CdpMessage {
id: Some(95),
method: "Log.stopViolationsReport".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_log_unknown() {
let msg = CdpMessage {
id: Some(96),
method: "Log.bogus".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
assert_eq!(resp.error.unwrap().code, -32601);
}
#[test]
fn handle_command_fetch_disable() {
let msg = CdpMessage {
id: Some(97),
method: "Fetch.disable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn handle_command_fetch_continue_with_response() {
let msg = CdpMessage {
id: Some(98),
method: "Fetch.continueWithResponse".into(),
params: Some(json!({"requestId": "r1"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn handle_command_fetch_fail_request() {
let msg = CdpMessage {
id: Some(99),
method: "Fetch.failRequest".into(),
params: Some(json!({"requestId": "r2", "reason": "Aborted"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn handle_command_fetch_fulfill_request() {
let msg = CdpMessage {
id: Some(100),
method: "Fetch.fulfillRequest".into(),
params: Some(json!({"requestId": "r3", "responseCode": 404, "body": "dGVzdA=="})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn handle_command_fetch_get_request_post_data() {
let msg = CdpMessage {
id: Some(101),
method: "Fetch.getRequestPostData".into(),
params: Some(json!({"requestId": "r4"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn handle_command_fetch_continue_with_auth() {
let msg = CdpMessage {
id: Some(102),
method: "Fetch.continueWithAuth".into(),
params: Some(json!({"requestId": "r5"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn handle_command_fetch_take_response_body_as_stream() {
let msg = CdpMessage {
id: Some(103),
method: "Fetch.takeResponseBodyAsStream".into(),
params: Some(json!({"requestId": "r6"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn handle_command_fetch_enable_without_patterns() {
let msg = CdpMessage {
id: Some(104),
method: "Fetch.enable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn handle_command_fetch_enable_with_multiple_patterns() {
let msg = CdpMessage {
id: Some(105),
method: "Fetch.enable".into(),
params: Some(json!({"patterns": [{"urlPattern": "*"}, {"urlPattern": "https://*"}]})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn handle_command_fetch_unknown() {
let msg = CdpMessage {
id: Some(106),
method: "Fetch.bogus".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
assert_eq!(resp.error.unwrap().code, -32601);
}
#[test]
fn params_str_nested_key_returns_empty() {
let params = Some(json!({"outer": {"inner": "value"}}));
let result = params_str(¶ms, "outer.inner");
assert_eq!(result, ""); }
#[test]
fn params_str_numeric_value_returns_empty() {
let params = Some(json!({"count": 42}));
let result = params_str(¶ms, "count");
assert_eq!(result, ""); }
#[test]
fn params_str_boolean_value_returns_empty() {
let params = Some(json!({"flag": true}));
let result = params_str(¶ms, "flag");
assert_eq!(result, ""); }
#[test]
fn params_str_null_value_returns_empty() {
let params = Some(json!({"key": null}));
let result = params_str(¶ms, "key");
assert_eq!(result, ""); }
#[test]
fn params_str_missing_key_returns_empty() {
let params = Some(json!({"other": "value"}));
let result = params_str(¶ms, "key");
assert_eq!(result, "");
}
#[test]
fn params_str_none_returns_empty() {
let result = params_str(&None, "key");
assert_eq!(result, "");
}
#[test]
fn params_str_empty_string_value() {
let params = Some(json!({"key": ""}));
let result = params_str(¶ms, "key");
assert_eq!(result, "");
}
#[test]
fn handle_command_bridge_required_no_bridge_returns_internal_error() {
let result = bridge_send(
None,
BridgeCommand::GetTitle {
target_id: "test-target".into(),
},
);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge connected"));
}
#[test]
fn cdp_message_debug_format() {
let msg = CdpMessage {
id: Some(1),
method: "Page.enable".into(),
params: None,
session_id: None,
};
let debug = format!("{:?}", msg);
assert!(debug.contains("CdpMessage"));
assert!(debug.contains("Page.enable"));
}
#[test]
fn cdp_message_clone() {
let msg = CdpMessage {
id: Some(1),
method: "Page.enable".into(),
params: Some(json!({"k": "v"})),
session_id: Some("s1".into()),
};
let cloned = msg.clone();
assert_eq!(cloned.id, msg.id);
assert_eq!(cloned.method, msg.method);
assert_eq!(cloned.params, msg.params);
assert_eq!(cloned.session_id, msg.session_id);
}
#[test]
fn service_worker_enable() {
let msg = CdpMessage {
id: Some(1),
method: "ServiceWorker.enable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn service_worker_disable() {
let msg = CdpMessage {
id: Some(2),
method: "ServiceWorker.disable".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
assert_eq!(resp.result.unwrap(), json!({}));
}
#[test]
fn service_worker_get_all_registrations_no_bridge() {
let msg = CdpMessage {
id: Some(3),
method: "ServiceWorker.getAllRegistrations".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["registrations"], json!([]));
}
#[test]
fn service_worker_get_registration_no_bridge() {
let msg = CdpMessage {
id: Some(4),
method: "ServiceWorker.getRegistration".into(),
params: Some(json!({"registrationId": "sw-reg-1"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["registration"], Value::Null);
}
#[test]
fn service_worker_stop_worker_no_bridge() {
let msg = CdpMessage {
id: Some(5),
method: "ServiceWorker.stopWorker".into(),
params: Some(json!({"registrationId": "sw-reg-1"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn service_worker_unregister_no_bridge() {
let msg = CdpMessage {
id: Some(6),
method: "ServiceWorker.unregister".into(),
params: Some(json!({"registrationId": "sw-reg-1"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn service_worker_deliver_push_message() {
let msg = CdpMessage {
id: Some(7),
method: "ServiceWorker.deliverPushMessage".into(),
params: Some(json!({"origin": "https://example.com", "registrationId": "sw-reg-1"})),
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
assert_eq!(result["origin"], "https://example.com");
assert_eq!(result["delivered"], true);
}
#[test]
fn service_worker_dispatch_periodic_sync_event() {
let msg = CdpMessage {
id: Some(8),
method: "ServiceWorker.dispatchPeriodicSyncEvent".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
}
#[test]
fn service_worker_unknown_command() {
let msg = CdpMessage {
id: Some(9),
method: "ServiceWorker.nonExistent".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.result.is_none());
let err = resp.error.unwrap();
assert_eq!(err.code, -32601);
}
#[test]
fn target_get_targets_no_bridge_includes_page() {
let msg = CdpMessage {
id: Some(10),
method: "Target.getTargets".into(),
params: None,
session_id: None,
};
let params = msg.params.clone();
let resp = handle_command(msg, "t1", ¶ms, None);
assert!(resp.error.is_none());
let result = resp.result.unwrap();
let infos = result["targetInfos"].as_array().unwrap();
assert!(infos.len() >= 1, "should at least have the page target");
assert_eq!(infos[0]["targetId"], "t1");
}
}