use crate::cdp::session::CdpSession;
use crate::error::{Error, Result};
use serde_json::{json, Value};
pub const INJECTED_SCRIPT: &str = include_str!("../assets/injected_script.js");
pub async fn count(session: &CdpSession, ctx: Option<i64>, selector: &str) -> Result<usize> {
let v = eval_context(
session,
ctx,
"(sel) => self.__pwcdpInjected.querySelectorAll(document, sel).length",
json!(selector),
)
.await?;
v.as_u64()
.map(|n| n as usize)
.ok_or_else(|| Error::ProtocolError(format!("selector count returned non-number: {v}")))
}
pub async fn element_at(
session: &CdpSession,
ctx: Option<i64>,
selector: &str,
index: usize,
) -> Result<Option<String>> {
eval_context_handle(
session,
ctx,
"(arg) => { const e = self.__pwcdpInjected.querySelectorAll(document, arg.sel); return arg.index < e.length ? e[arg.index] : undefined; }",
json!({ "sel": selector, "index": index }),
)
.await
}
#[allow(dead_code)]
pub(crate) async fn frame_chain_document_handle(
session: &CdpSession,
ctx: Option<i64>,
frame_chain: &[String],
) -> Result<Option<String>> {
eval_context_handle(
session,
ctx,
"(arg) => { let root = document; for (const fs of arg.chain) { const f = self.__pwcdpInjected.querySelectorAll(root, fs)[0]; root = f && f.contentDocument; if (!root) return undefined; } return root; }",
json!({ "chain": frame_chain }),
)
.await
}
pub(crate) async fn count_in(
session: &CdpSession,
ctx: Option<i64>,
frame_chain: &[String],
selector: &str,
) -> Result<usize> {
let v = eval_context(
session,
ctx,
"(arg) => { let root = document; for (const fs of arg.chain) { const f = self.__pwcdpInjected.querySelectorAll(root, fs)[0]; root = f && f.contentDocument; if (!root) return 0; } return self.__pwcdpInjected.querySelectorAll(root, arg.sel).length; }",
json!({ "chain": frame_chain, "sel": selector }),
)
.await?;
v.as_u64()
.map(|n| n as usize)
.ok_or_else(|| Error::ProtocolError(format!("selector count returned non-number: {v}")))
}
pub(crate) async fn element_at_in(
session: &CdpSession,
ctx: Option<i64>,
frame_chain: &[String],
selector: &str,
index: usize,
) -> Result<Option<String>> {
eval_context_handle(
session,
ctx,
"(arg) => { let root = document; for (const fs of arg.chain) { const f = self.__pwcdpInjected.querySelectorAll(root, fs)[0]; root = f && f.contentDocument; if (!root) return undefined; } const e = self.__pwcdpInjected.querySelectorAll(root, arg.sel); return arg.index < e.length ? e[arg.index] : undefined; }",
json!({ "chain": frame_chain, "sel": selector, "index": index }),
)
.await
}
pub(crate) async fn eval_context(
session: &CdpSession,
ctx: Option<i64>,
function: &str,
arg: Value,
) -> Result<Value> {
let expr = format!("({})({})", function, serde_json::to_string(&arg)?);
let mut params = json!({
"expression": expr,
"returnByValue": true,
"awaitPromise": true,
});
if let Some(id) = ctx {
params["contextId"] = json!(id);
}
let resp = session.send("Runtime.evaluate", params).await?;
if let Some(exc) = resp.get("exceptionDetails") {
let msg = exc
.get("exception")
.and_then(|e| e.get("description"))
.and_then(|v| v.as_str())
.unwrap_or("evaluation threw");
return Err(Error::ProtocolError(format!("eval error: {msg}")));
}
Ok(resp
.get("result")
.and_then(|r| r.get("value"))
.cloned()
.unwrap_or(Value::Null))
}
pub(crate) async fn eval_context_handle(
session: &CdpSession,
ctx: Option<i64>,
function: &str,
arg: Value,
) -> Result<Option<String>> {
let expr = format!("({})({})", function, serde_json::to_string(&arg)?);
let mut params = json!({
"expression": expr,
"returnByValue": false,
"awaitPromise": true,
});
if let Some(id) = ctx {
params["contextId"] = json!(id);
}
let resp = session.send("Runtime.evaluate", params).await?;
if let Some(exc) = resp.get("exceptionDetails") {
let msg = exc
.get("exception")
.and_then(|e| e.get("description"))
.and_then(|v| v.as_str())
.unwrap_or("evaluation threw");
return Err(Error::ProtocolError(format!("eval error: {msg}")));
}
Ok(resp
.get("result")
.and_then(|r| r.get("objectId"))
.and_then(|v| v.as_str())
.map(|s| s.to_string()))
}
pub(crate) async fn eval_object(
session: &CdpSession,
object_id: &str,
function: &str,
arg: Value,
) -> Result<Value> {
let params = json!({
"objectId": object_id,
"functionDeclaration": function,
"arguments": [{ "objectId": object_id }, { "value": arg }],
"returnByValue": true,
"awaitPromise": true,
});
let result = call(session, params).await?;
Ok(result.get("value").cloned().unwrap_or(Value::Null))
}
async fn call(session: &CdpSession, params: Value) -> Result<Value> {
let resp = session.send("Runtime.callFunctionOn", params).await?;
Ok(resp.get("result").cloned().unwrap_or(Value::Null))
}