use crate::domains::{DispatchContext, DomainResult};
use crate::event::EventSender;
use crate::protocol::CdpError;
use serde_json::{Value, json};
pub async fn handle(method: &str, params: Option<Value>, ctx: &DispatchContext) -> DomainResult {
match method {
"enable" => enable(ctx),
"disable" => disable(ctx),
"evaluate" => evaluate(params, ctx).await,
"callFunctionOn" => call_function_on(params, ctx).await,
"getProperties" => get_properties(params),
"compileScript" => Ok(Some(json!({ "scriptId": "", "exceptionDetails": null }))),
"runScript" => Ok(Some(json!({
"result": { "type": "undefined" },
"exceptionDetails": null
}))),
_ => Err(CdpError {
code: -32601,
message: format!("Runtime.{} not implemented", method),
}),
}
}
fn enable(ctx: &DispatchContext) -> DomainResult {
ctx.events.set_runtime_enabled(true);
ctx.events.send_runtime_event(
"Runtime.executionContextCreated",
json!({
"context": {
"id": 1,
"origin": "",
"name": "main",
"uniqueId": format!("context-{}", uuid::Uuid::new_v4()),
"auxData": {
"isDefault": true,
"type": "default"
}
}
}),
);
Ok(Some(json!({})))
}
fn disable(ctx: &DispatchContext) -> DomainResult {
ctx.events.set_runtime_enabled(false);
Ok(Some(json!({})))
}
async fn evaluate(params: Option<Value>, ctx: &DispatchContext) -> DomainResult {
let params = params.unwrap_or_default();
let expression = params
.get("expression")
.and_then(|v| v.as_str())
.unwrap_or("");
let return_by_value = params
.get("returnByValue")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let await_promise = params
.get("awaitPromise")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let mut guard = ctx.session.write().await;
match guard
.evaluate_js_with_await(expression, await_promise)
.await
{
Ok(result) => {
if let Some(exception) = &result.exception {
return Ok(Some(json!({
"result": { "type": "undefined" },
"exceptionDetails": {
"text": exception,
"exception": { "type": "string", "value": exception }
}
})));
}
let value = result.value.unwrap_or(Value::Null);
let result_type = classify_json_type(&value);
if !result.console_output.is_empty() {
let args: Vec<Value> = result
.console_output
.iter()
.map(|msg| {
json!({
"type": "string",
"value": msg,
"description": msg
})
})
.collect();
ctx.events.send_runtime_event(
"Runtime.consoleAPICalled",
json!({
"type": "log",
"args": args,
"executionContextId": 1,
"timestamp": EventSender::timestamp_ms()
}),
);
}
let _ = return_by_value;
Ok(Some(json!({
"result": {
"type": result_type,
"value": value,
},
"exceptionDetails": null
})))
}
Err(e) => Ok(Some(json!({
"result": { "type": "undefined" },
"exceptionDetails": {
"text": e.to_string(),
"exception": { "type": "string", "value": e.to_string() }
}
}))),
}
}
async fn call_function_on(params: Option<Value>, ctx: &DispatchContext) -> DomainResult {
let params = params.unwrap_or_default();
let function_declaration = params
.get("functionDeclaration")
.and_then(|v| v.as_str())
.unwrap_or("");
let object_id = params
.get("objectId")
.and_then(|v| v.as_str())
.unwrap_or("");
let return_by_value = params
.get("returnByValue")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let arguments: Vec<Value> = params
.get("arguments")
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
let args_str = build_args_expression(&arguments, object_id);
let expr = if object_id.starts_with("oxi-node-") {
let node_id_str = object_id.strip_prefix("oxi-node-").unwrap_or("0");
format!(
"(function() {{ var __fn = {func}; var __el = document.querySelector('[data-oxi-node-id=\"{nid}\"]') || document.body; return __fn(__el{args}); }})()",
func = function_declaration,
nid = node_id_str,
args = if args_str.is_empty() {
String::new()
} else {
format!(", {}", args_str)
}
)
} else if object_id.is_empty() {
format!(
"(function() {{ var __fn = {func}; return __fn({args}); }})()",
func = function_declaration,
args = args_str
)
} else {
format!(
"(function() {{ var __fn = {func}; return __fn(undefined{args}); }})()",
func = function_declaration,
args = if args_str.is_empty() {
String::new()
} else {
format!(", {}", args_str)
}
)
};
let mut guard = ctx.session.write().await;
match guard.evaluate_js(&expr).await {
Ok(result) => {
if let Some(exception) = &result.exception {
return Ok(Some(json!({
"result": { "type": "undefined" },
"exceptionDetails": {
"text": exception,
"exception": { "type": "string", "value": exception }
}
})));
}
let value = result.value.unwrap_or(Value::Null);
let result_type = classify_json_type(&value);
if !result.console_output.is_empty() {
let console_args: Vec<Value> = result
.console_output
.iter()
.map(|msg| {
json!({
"type": "string",
"value": msg,
"description": msg
})
})
.collect();
ctx.events.send_runtime_event(
"Runtime.consoleAPICalled",
json!({
"type": "log",
"args": console_args,
"executionContextId": 1,
"timestamp": EventSender::timestamp_ms()
}),
);
}
if return_by_value {
Ok(Some(json!({
"result": {
"type": result_type,
"value": value,
},
"exceptionDetails": null
})))
} else {
if result_type == "object" && !value.is_null() {
Ok(Some(json!({
"result": {
"type": "object",
"value": value,
"description": "Object",
},
"exceptionDetails": null
})))
} else {
Ok(Some(json!({
"result": {
"type": result_type,
"value": value,
},
"exceptionDetails": null
})))
}
}
}
Err(e) => Ok(Some(json!({
"result": { "type": "undefined" },
"exceptionDetails": {
"text": e.to_string(),
"exception": { "type": "string", "value": e.to_string() }
}
}))),
}
}
fn build_args_expression(arguments: &[Value], _this_object_id: &str) -> String {
let parts: Vec<String> = arguments
.iter()
.map(|arg| {
if let Some(val) = arg.get("value") {
serde_json::to_string(val).unwrap_or_else(|_| "undefined".to_string())
} else if let Some(obj_id) = arg.get("objectId").and_then(|v| v.as_str()) {
if obj_id.starts_with("oxi-node-") {
let node_id_str = obj_id.strip_prefix("oxi-node-").unwrap_or("0");
format!(
"document.querySelector('[data-oxi-node-id=\"{}\"]') || document.body",
node_id_str
)
} else {
"undefined".to_string()
}
} else if let Some(unsv) = arg.get("unserializableValue").and_then(|v| v.as_str()) {
unsv.to_string()
} else {
"undefined".to_string()
}
})
.collect();
parts.join(", ")
}
fn get_properties(params: Option<Value>) -> DomainResult {
let params = params.unwrap_or_default();
let _object_id = params
.get("objectId")
.and_then(|v| v.as_str())
.unwrap_or("");
Ok(Some(json!({
"result": [],
"exceptionDetails": null
})))
}
fn classify_json_type(value: &Value) -> &'static str {
match value {
Value::String(_) => "string",
Value::Number(_) => "number",
Value::Bool(_) => "boolean",
Value::Null => "object",
Value::Array(_) => "object",
Value::Object(_) => "object",
}
}