///|
/// Protocol-First Executor
///
/// All browser operations MUST go through this module.
/// This ensures:
/// 1. Consistent protocol handling
/// 2. Operation logging for debug/replay
/// 3. Single source of truth for all browser control
// =============================================================================
// Execution Context
// =============================================================================
///|
/// Execution context with logging and state management
pub struct Executor {
manager : CdpSessionManager
/// Enable operation logging
mut logging_enabled : Bool
/// Operation log (JSON-RPC format)
operation_log : Array[String]
}
///|
/// Create a new executor
pub fn Executor::new() -> Executor {
{
manager: CdpSessionManager::new(),
logging_enabled: false,
operation_log: [],
}
}
///|
/// Create executor with custom viewport (viewport info stored but not used in pure protocol mode)
pub fn Executor::new_with_viewport(_width : Int, _height : Int) -> Executor {
{
manager: CdpSessionManager::new(),
logging_enabled: false,
operation_log: [],
}
}
///|
/// Enable operation logging
pub fn Executor::enable_logging(self : Executor) -> Unit {
self.logging_enabled = true
}
///|
/// Disable operation logging
pub fn Executor::disable_logging(self : Executor) -> Unit {
self.logging_enabled = false
}
///|
/// Get operation log
pub fn Executor::get_log(self : Executor) -> Array[String] {
self.operation_log
}
///|
/// Clear operation log
pub fn Executor::clear_log(self : Executor) -> Unit {
self.operation_log.clear()
}
// =============================================================================
// Core Execute Function - ALL OPERATIONS GO THROUGH HERE
// =============================================================================
///|
/// Execute an API method and return result
/// This is THE central entry point for all browser operations.
pub fn Executor::execute(
self : Executor,
api_method : ApiMethod,
) -> ApiResult[String] {
// Log the operation if enabled
if self.logging_enabled {
self.log_method(api_method)
}
// Execute via the handler
let request = method_to_request(api_method)
let response = handle_rpc(self.manager, request)
// Convert RPC response to ApiResult
let result = rpc_response_to_result(response)
// Log the result if enabled
if self.logging_enabled {
self.log_result(result)
}
result
}
///|
/// Execute from JSON-RPC request string
pub fn Executor::execute_json(self : Executor, json : String) -> String {
// Parse JSON to RpcRequest
let request = parse_rpc_request(json)
// Log raw request if enabled
if self.logging_enabled {
self.operation_log.push("REQ: " + json)
}
// Execute
let response = handle_rpc(self.manager, request)
let response_json = response.to_json()
// Log response if enabled
if self.logging_enabled {
self.operation_log.push("RES: " + response_json)
}
response_json
}
// =============================================================================
// Convenience Methods - All delegate to execute()
// =============================================================================
///|
/// Create a new browser context
pub fn Executor::new_context(self : Executor) -> ApiResult[String] {
self.execute(NewContext)
}
///|
/// Close a browser context
pub fn Executor::close_context(
self : Executor,
context_id : String,
) -> ApiResult[String] {
self.execute(CloseContext(context_id~))
}
///|
/// Navigate to URL
pub fn Executor::goto(
self : Executor,
page_id : String,
url : String,
) -> ApiResult[String] {
self.execute(Goto(page_id~, url~))
}
///|
/// Go back in history
pub fn Executor::go_back(
self : Executor,
page_id : String,
) -> ApiResult[String] {
self.execute(GoBack(page_id~))
}
///|
/// Go forward in history
pub fn Executor::go_forward(
self : Executor,
page_id : String,
) -> ApiResult[String] {
self.execute(GoForward(page_id~))
}
///|
/// Reload page
pub fn Executor::reload(self : Executor, page_id : String) -> ApiResult[String] {
self.execute(Reload(page_id~))
}
///|
/// Get current URL
pub fn Executor::url(self : Executor, page_id : String) -> ApiResult[String] {
self.execute(Url(page_id~))
}
///|
/// Get page title
pub fn Executor::title(self : Executor, page_id : String) -> ApiResult[String] {
self.execute(Title(page_id~))
}
///|
/// Get page content
pub fn Executor::content(
self : Executor,
page_id : String,
) -> ApiResult[String] {
self.execute(Content(page_id~))
}
///|
/// Get screenshot (DOM tree as text in pure protocol mode)
pub fn Executor::screenshot(
self : Executor,
page_id : String,
) -> ApiResult[String] {
self.execute(Screenshot(page_id~))
}
// =============================================================================
// Internal Helpers
// =============================================================================
///|
/// Convert ApiMethod to RpcRequest
fn method_to_request(api_method : ApiMethod) -> RpcRequest {
let method_name = api_method_to_string(api_method)
let params = api_method_to_params(api_method)
{ jsonrpc: "2.0", id: RpcId::Num(0), method_name, params }
}
///|
/// Get method name string from ApiMethod
fn api_method_to_string(api_method : ApiMethod) -> String {
match api_method {
NewContext => "newContext"
CloseContext(..) => "closeContext"
NewPage(..) => "newPage"
ClosePage(..) => "closePage"
Pages(..) => "pages"
Goto(..) => "goto"
GoBack(..) => "goBack"
GoForward(..) => "goForward"
Reload(..) => "reload"
Url(..) => "url"
Title(..) => "title"
Query(..) => "query"
QueryAll(..) => "queryAll"
WaitFor(..) => "waitFor"
BoundingBox(..) => "boundingBox"
TextContent(..) => "textContent"
InnerText(..) => "innerText"
InnerHtml(..) => "innerHtml"
GetAttribute(..) => "getAttribute"
IsVisible(..) => "isVisible"
IsEnabled(..) => "isEnabled"
IsChecked(..) => "isChecked"
Click(..) => "click"
DblClick(..) => "dblClick"
RightClick(..) => "rightClick"
Hover(..) => "hover"
Focus(..) => "focus"
Fill(..) => "fill"
Press(..) => "press"
Clear(..) => "clear"
SelectOption(..) => "selectOption"
Check(..) => "check"
Uncheck(..) => "uncheck"
MouseMove(..) => "mouseMove"
MouseClick(..) => "mouseClick"
MouseDown(..) => "mouseDown"
MouseUp(..) => "mouseUp"
KeyboardPress(..) => "keyboardPress"
KeyboardType(..) => "keyboardType"
Content(..) => "content"
SetContent(..) => "setContent"
ViewportSize(..) => "viewportSize"
SetViewportSize(..) => "setViewportSize"
Evaluate(..) => "evaluate"
Screenshot(..) => "screenshot"
ElementScreenshot(..) => "elementScreenshot"
AccessibilitySnapshot(..) => "accessibilitySnapshot"
}
}
///|
/// Extract params from ApiMethod
fn api_method_to_params(api_method : ApiMethod) -> Map[String, String] {
let params : Map[String, String] = {}
match api_method {
NewContext => ()
CloseContext(context_id~) => params["context_id"] = context_id
NewPage(context_id~) => params["context_id"] = context_id
ClosePage(page_id~) => params["page_id"] = page_id
Pages(context_id~) => params["context_id"] = context_id
Goto(page_id~, url~) => {
params["page_id"] = page_id
params["url"] = url
}
GoBack(page_id~) => params["page_id"] = page_id
GoForward(page_id~) => params["page_id"] = page_id
Reload(page_id~) => params["page_id"] = page_id
Url(page_id~) => params["page_id"] = page_id
Title(page_id~) => params["page_id"] = page_id
Query(page_id~, selector~) => {
params["page_id"] = page_id
params["selector"] = selector
}
QueryAll(page_id~, selector~) => {
params["page_id"] = page_id
params["selector"] = selector
}
WaitFor(page_id~, selector~, timeout~) => {
params["page_id"] = page_id
params["selector"] = selector
params["timeout"] = timeout.to_string()
}
BoundingBox(element_id~) => params["element_id"] = element_id
TextContent(element_id~) => params["element_id"] = element_id
InnerText(element_id~) => params["element_id"] = element_id
InnerHtml(element_id~) => params["element_id"] = element_id
GetAttribute(element_id~, name~) => {
params["element_id"] = element_id
params["name"] = name
}
IsVisible(element_id~) => params["element_id"] = element_id
IsEnabled(element_id~) => params["element_id"] = element_id
IsChecked(element_id~) => params["element_id"] = element_id
Click(element_id~) => params["element_id"] = element_id
DblClick(element_id~) => params["element_id"] = element_id
RightClick(element_id~) => params["element_id"] = element_id
Hover(element_id~) => params["element_id"] = element_id
Focus(element_id~) => params["element_id"] = element_id
Fill(element_id~, value~) => {
params["element_id"] = element_id
params["value"] = value
}
Press(element_id~, key~) => {
params["element_id"] = element_id
params["key"] = key
}
Clear(element_id~) => params["element_id"] = element_id
SelectOption(element_id~, value~) => {
params["element_id"] = element_id
params["value"] = value
}
Check(element_id~) => params["element_id"] = element_id
Uncheck(element_id~) => params["element_id"] = element_id
MouseMove(page_id~, x~, y~) => {
params["page_id"] = page_id
params["x"] = x.to_string()
params["y"] = y.to_string()
}
MouseClick(page_id~, x~, y~, button~) => {
params["page_id"] = page_id
params["x"] = x.to_string()
params["y"] = y.to_string()
params["button"] = button
}
MouseDown(page_id~, button~) => {
params["page_id"] = page_id
params["button"] = button
}
MouseUp(page_id~, button~) => {
params["page_id"] = page_id
params["button"] = button
}
KeyboardPress(page_id~, key~) => {
params["page_id"] = page_id
params["key"] = key
}
KeyboardType(page_id~, text~) => {
params["page_id"] = page_id
params["text"] = text
}
Content(page_id~) => params["page_id"] = page_id
SetContent(page_id~, html~) => {
params["page_id"] = page_id
params["html"] = html
}
ViewportSize(page_id~) => params["page_id"] = page_id
SetViewportSize(page_id~, width~, height~) => {
params["page_id"] = page_id
params["width"] = width.to_string()
params["height"] = height.to_string()
}
Evaluate(page_id~, expression~) => {
params["page_id"] = page_id
params["expression"] = expression
}
Screenshot(page_id~) => params["page_id"] = page_id
ElementScreenshot(element_id~) => params["element_id"] = element_id
AccessibilitySnapshot(page_id~) => params["page_id"] = page_id
}
params
}
///|
/// Convert RpcResponse to ApiResult
fn rpc_response_to_result(response : RpcResponse) -> ApiResult[String] {
match response {
Success(result~, ..) => ApiResult::Ok(result)
Error(message~, ..) => ApiResult::Err(InternalError(message~))
}
}
///|
/// Parse JSON string to RpcRequest (simplified)
fn parse_rpc_request(json : String) -> RpcRequest {
let obj = parse_json_object(json)
let id = match obj.get("id") {
Some(id_str) => {
// Try to parse as integer
let parsed = @string.parse_int(id_str) catch { _ => -1 }
if parsed >= 0 {
RpcId::Num(parsed)
} else {
RpcId::Str(id_str)
}
}
None => RpcId::Null
}
let method_name = obj.get("method").unwrap_or("")
// Parse params object if present
let params : Map[String, String] = {}
// For now, use simple key extraction from the JSON
// A full implementation would parse nested objects
{ jsonrpc: obj.get("jsonrpc").unwrap_or("2.0"), id, method_name, params }
}
///|
/// Log an API method call
fn Executor::log_method(self : Executor, api_method : ApiMethod) -> Unit {
let method_name = api_method_to_string(api_method)
let params = api_method_to_params(api_method)
let buf = StringBuilder::new()
buf.write_string("CALL: ")
buf.write_string(method_name)
buf.write_string("(")
let mut first = true
for key, value in params {
if !first {
buf.write_string(", ")
}
buf.write_string(key)
buf.write_string("=")
buf.write_string(value)
first = false
}
buf.write_string(")")
self.operation_log.push(buf.to_string())
}
///|
/// Log a result
fn Executor::log_result(self : Executor, result : ApiResult[String]) -> Unit {
match result {
ApiResult::Ok(value) => self.operation_log.push("OK: " + value)
ApiResult::Err(err) => self.operation_log.push("ERR: " + err.to_string())
}
}