///|
/// BiDi client
///|
/// BiDi client state
pub struct BidiClient {
transport : Transport
mut next_id : Int
mut connected : Bool
mut has_subscription_filter : Bool
pending : Map[Int, BidiResponse]
event_handlers : Array[(BidiEvent) -> Unit]
global_subscriptions : Map[String, Bool]
context_subscriptions : Map[String, Map[String, Bool]]
}
///|
/// Create a new client
pub fn BidiClient::new(transport : Transport) -> BidiClient {
{
transport,
next_id: 1,
connected: false,
has_subscription_filter: false,
pending: {},
event_handlers: [],
global_subscriptions: {},
context_subscriptions: {},
}
}
///|
/// Register event handler
pub fn BidiClient::on_event(
self : BidiClient,
handler : (BidiEvent) -> Unit,
) -> Unit {
self.event_handlers.push(handler)
}
///|
/// Register handler for a specific event method
pub fn BidiClient::on_event_method(
self : BidiClient,
event_method : String,
handler : (BidiEvent) -> Unit,
) -> Unit {
self.on_event(fn(evt : BidiEvent) {
if evt.event_method == event_method {
handler(evt)
}
})
}
///|
/// Register handler for session.* events
pub fn BidiClient::on_session_event(
self : BidiClient,
handler : (BidiEvent) -> Unit,
) -> Unit {
self.on_event(fn(evt : BidiEvent) {
if evt.event_method.has_prefix("session.") {
handler(evt)
}
})
}
///|
/// Register handler for browser.* events
pub fn BidiClient::on_browser_event(
self : BidiClient,
handler : (BidiEvent) -> Unit,
) -> Unit {
self.on_event(fn(evt : BidiEvent) {
if evt.event_method.has_prefix("browser.") {
handler(evt)
}
})
}
///|
/// Register handler for browsingContext.* events
pub fn BidiClient::on_browsing_context_event(
self : BidiClient,
handler : (BidiEvent) -> Unit,
) -> Unit {
self.on_event(fn(evt : BidiEvent) {
if evt.event_method.has_prefix("browsingContext.") {
handler(evt)
}
})
}
///|
/// Register handler for script.* events
pub fn BidiClient::on_script_event(
self : BidiClient,
handler : (BidiEvent) -> Unit,
) -> Unit {
self.on_event(fn(evt : BidiEvent) {
if evt.event_method.has_prefix("script.") {
handler(evt)
}
})
}
///|
/// Register handler for input.* events
pub fn BidiClient::on_input_event(
self : BidiClient,
handler : (BidiEvent) -> Unit,
) -> Unit {
self.on_event(fn(evt : BidiEvent) {
if evt.event_method.has_prefix("input.") {
handler(evt)
}
})
}
///|
/// Register handler for network.* events
pub fn BidiClient::on_network_event(
self : BidiClient,
handler : (BidiEvent) -> Unit,
) -> Unit {
self.on_event(fn(evt : BidiEvent) {
if evt.event_method.has_prefix("network.") {
handler(evt)
}
})
}
///|
/// Register handler for log.* events
pub fn BidiClient::on_log_event(
self : BidiClient,
handler : (BidiEvent) -> Unit,
) -> Unit {
self.on_event(fn(evt : BidiEvent) {
if evt.event_method.has_prefix("log.") {
handler(evt)
}
})
}
///|
/// Register handler for log.entryAdded (optional level filter)
pub fn BidiClient::on_log_entry_added(
self : BidiClient,
level? : String,
handler : (BidiEvent) -> Unit,
) -> Unit {
self.on_log_event(fn(evt : BidiEvent) {
if evt.event_method != "log.entryAdded" {
return ()
}
match level {
Some(expected) =>
match get_string_field(evt.params, "level") {
Some(actual) => if actual == expected { handler(evt) }
None => ()
}
None => handler(evt)
}
})
}
///|
/// Connect transport
pub async fn BidiClient::connect(
self : BidiClient,
) -> Result[Unit, ClientError] {
if self.connected {
return Ok(())
}
match self.transport.connect() {
Ok(_) => {
self.connected = true
Ok(())
}
Err(e) => Err(Transport(e))
}
}
///|
/// Close transport
pub fn BidiClient::close(self : BidiClient) -> Result[Unit, ClientError] {
self.connected = false
match self.transport.close() {
Ok(_) => Ok(())
Err(e) => Err(Transport(e))
}
}
///|
/// Send raw request and await response
pub async fn BidiClient::send(
self : BidiClient,
method_name : String,
params? : Json? = None,
) -> Result[Json, ClientError] {
match self.connect() {
Ok(_) => ()
Err(e) => return Err(e)
}
let id = self.next_id
self.next_id = self.next_id + 1
let request : BidiRequest = { id, method_name, params }
match self.transport.send(request.to_json()) {
Ok(_) => ()
Err(e) => return Err(Transport(e))
}
let response = self.wait_response(id)
match response {
Ok(resp) =>
match resp.error {
Some(err) => Err(ResponseError(error=err))
None =>
match resp.result {
Some(result) => Ok(result)
None => Ok(Json::object({}))
}
}
Err(e) => Err(e)
}
}
///|
/// Dispatch request through the domain router
pub async fn BidiClient::dispatch(
self : BidiClient,
domain : String,
command : String,
params? : Json? = None,
) -> Result[Json, ClientError] {
if !(is_supported_domain_command(domain, command)) {
return Err(unknown_command_error(domain, command))
}
self.send(domain + "." + command, params~)
}
///|
/// Session: status
pub async fn BidiClient::session_status(
self : BidiClient,
) -> Result[SessionStatus, ClientError] {
let result = self.dispatch("session", "status")
match result {
Ok(json) =>
match get_bool_field(json, "ready") {
Some(ready) => {
let message = get_string_field(json, "message")
Ok({ ready, message })
}
None => Err(ParseError(message="Missing ready field"))
}
Err(e) => Err(e)
}
}
///|
/// Session: new
pub async fn BidiClient::session_new(
self : BidiClient,
capabilities? : Json? = None,
) -> Result[SessionInfo, ClientError] {
match capabilities {
Some(caps) =>
match caps {
Object(_) => ()
_ =>
return Err(
invalid_argument_error("capabilities must be a JSON object"),
)
}
None => ()
}
let result = match capabilities {
Some(caps) => {
let params : Map[String, Json] = {}
params["capabilities"] = caps
self.dispatch("session", "new", params=Some(Json::object(params)))
}
None => self.dispatch("session", "new")
}
match result {
Ok(json) =>
match get_string_field(json, "sessionId") {
Some(session_id) => {
let caps = get_object_field(json, "capabilities")
Ok({ session_id, capabilities: caps })
}
None => Err(ParseError(message="Missing sessionId field"))
}
Err(e) => Err(e)
}
}
///|
/// Session: subscribe
pub async fn BidiClient::session_subscribe(
self : BidiClient,
events : Array[String],
contexts? : Array[String],
) -> Result[Unit, ClientError] {
match validate_subscription_request(events, contexts) {
Ok(_) => ()
Err(e) => return Err(e)
}
let json_events : Array[Json] = []
for e in events {
json_events.push(Json::string(e))
}
let params : Map[String, Json] = {}
params["events"] = Json::array(json_events)
match contexts {
Some(context_ids) => {
let json_contexts : Array[Json] = []
for context_id in context_ids {
json_contexts.push(Json::string(context_id))
}
params["contexts"] = Json::array(json_contexts)
}
None => ()
}
match
self.dispatch("session", "subscribe", params=Some(Json::object(params))) {
Ok(_) => {
self.apply_subscribe(events, contexts)
Ok(())
}
Err(e) => Err(e)
}
}
///|
/// Session: unsubscribe
pub async fn BidiClient::session_unsubscribe(
self : BidiClient,
events : Array[String],
contexts? : Array[String],
) -> Result[Unit, ClientError] {
match validate_subscription_request(events, contexts) {
Ok(_) => ()
Err(e) => return Err(e)
}
let json_events : Array[Json] = []
for e in events {
json_events.push(Json::string(e))
}
let params : Map[String, Json] = {}
params["events"] = Json::array(json_events)
match contexts {
Some(context_ids) => {
let json_contexts : Array[Json] = []
for context_id in context_ids {
json_contexts.push(Json::string(context_id))
}
params["contexts"] = Json::array(json_contexts)
}
None => ()
}
match
self.dispatch("session", "unsubscribe", params=Some(Json::object(params))) {
Ok(_) => {
self.apply_unsubscribe(events, contexts)
Ok(())
}
Err(e) => Err(e)
}
}
///|
/// Session: end
pub async fn BidiClient::session_end(
self : BidiClient,
) -> Result[Unit, ClientError] {
match self.dispatch("session", "end") {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// browser.getUserContexts
pub async fn BidiClient::browser_get_user_contexts(
self : BidiClient,
) -> Result[Array[String], ClientError] {
let result = self.dispatch("browser", "getUserContexts")
match result {
Ok(json) =>
match get_array_field(json, "userContexts") {
Some(items) => {
let contexts : Array[String] = []
for item in items {
match get_string_field(item, "userContext") {
Some(id) => contexts.push(id)
None => ()
}
}
Ok(contexts)
}
None => Err(ParseError(message="Missing userContexts field"))
}
Err(e) => Err(e)
}
}
///|
/// browser.createUserContext
pub async fn BidiClient::browser_create_user_context(
self : BidiClient,
) -> Result[String, ClientError] {
let result = self.dispatch("browser", "createUserContext")
match result {
Ok(json) =>
match get_string_field(json, "userContext") {
Some(id) => Ok(id)
None => Err(ParseError(message="Missing userContext field"))
}
Err(e) => Err(e)
}
}
///|
/// browser.removeUserContext
pub async fn BidiClient::browser_remove_user_context(
self : BidiClient,
user_context : String,
) -> Result[Unit, ClientError] {
match validate_non_empty_string(user_context, "userContext") {
Ok(_) => ()
Err(e) => return Err(e)
}
let params : Map[String, Json] = {}
params["userContext"] = Json::string(user_context)
match
self.dispatch(
"browser",
"removeUserContext",
params=Some(Json::object(params)),
) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// browser.close
pub async fn BidiClient::browser_close(
self : BidiClient,
) -> Result[Unit, ClientError] {
match self.dispatch("browser", "close") {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// browsingContext.create
pub async fn BidiClient::browsing_context_create(
self : BidiClient,
context_type : String,
user_context? : String,
) -> Result[String, ClientError] {
if context_type != "tab" && context_type != "window" {
return Err(invalid_argument_error("type must be one of tab|window"))
}
match user_context {
Some(value) =>
match validate_non_empty_string(value, "userContext") {
Ok(_) => ()
Err(e) => return Err(e)
}
None => ()
}
let params : Map[String, Json] = {}
params["type"] = Json::string(context_type)
match user_context {
Some(u) => params["userContext"] = Json::string(u)
None => ()
}
let result = self.dispatch(
"browsingContext",
"create",
params=Some(Json::object(params)),
)
match result {
Ok(json) =>
match get_string_field(json, "context") {
Some(ctx) => Ok(ctx)
None => Err(ParseError(message="Missing context field"))
}
Err(e) => Err(e)
}
}
///|
/// browsingContext.reload
pub async fn BidiClient::browsing_context_reload(
self : BidiClient,
context_id : String,
) -> Result[Unit, ClientError] {
match validate_non_empty_string(context_id, "context") {
Ok(_) => ()
Err(e) => return Err(e)
}
let params : Map[String, Json] = {}
params["context"] = Json::string(context_id)
match
self.dispatch(
"browsingContext",
"reload",
params=Some(Json::object(params)),
) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// browsingContext.close
pub async fn BidiClient::browsing_context_close(
self : BidiClient,
context_id : String,
) -> Result[Unit, ClientError] {
match validate_non_empty_string(context_id, "context") {
Ok(_) => ()
Err(e) => return Err(e)
}
let params : Map[String, Json] = {}
params["context"] = Json::string(context_id)
match
self.dispatch("browsingContext", "close", params=Some(Json::object(params))) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// browsingContext.getTree
pub async fn BidiClient::browsing_context_get_tree(
self : BidiClient,
root? : String,
max_depth? : Int,
) -> Result[Json, ClientError] {
let params : Map[String, Json] = {}
let mut has_params = false
match root {
Some(value) =>
if value.is_empty() {
return Err(invalid_argument_error("root must not be empty"))
} else {
params["root"] = Json::string(value)
has_params = true
}
None => ()
}
match max_depth {
Some(depth) =>
if depth < 0 {
return Err(invalid_argument_error("maxDepth must be non-negative"))
} else {
params["maxDepth"] = Json::number(depth.to_double())
has_params = true
}
None => ()
}
if has_params {
self.dispatch(
"browsingContext",
"getTree",
params=Some(Json::object(params)),
)
} else {
self.dispatch("browsingContext", "getTree")
}
}
///|
/// browsingContext.navigate
pub async fn BidiClient::browsing_context_navigate(
self : BidiClient,
context_id : String,
url : String,
wait? : String = "complete",
) -> Result[NavigationResult, ClientError] {
match validate_non_empty_string(context_id, "context") {
Ok(_) => ()
Err(e) => return Err(e)
}
match validate_non_empty_string(url, "url") {
Ok(_) => ()
Err(e) => return Err(e)
}
if !(is_valid_navigation_wait(wait)) {
return Err(
invalid_argument_error("wait must be one of none|interactive|complete"),
)
}
let params : Map[String, Json] = {}
params["context"] = Json::string(context_id)
params["url"] = Json::string(url)
params["wait"] = Json::string(wait)
let result = self.dispatch(
"browsingContext",
"navigate",
params=Some(Json::object(params)),
)
match result {
Ok(json) => {
let navigation = get_string_field(json, "navigation")
let result_url = get_string_field(json, "url")
match (navigation, result_url) {
(Some(nav), Some(u)) => Ok({ navigation: nav, url: u })
_ => Err(ParseError(message="Missing navigation/url field"))
}
}
Err(e) => Err(e)
}
}
///|
/// browsingContext.captureScreenshot
pub async fn BidiClient::browsing_context_capture_screenshot(
self : BidiClient,
context_id : String,
origin? : String,
format? : String,
quality? : Int,
clip? : Json,
) -> Result[String, ClientError] {
match validate_non_empty_string(context_id, "context") {
Ok(_) => ()
Err(e) => return Err(e)
}
let params : Map[String, Json] = {}
params["context"] = Json::string(context_id)
match origin {
Some(value) =>
if value != "viewport" && value != "document" {
return Err(
invalid_argument_error("origin must be one of viewport|document"),
)
} else {
params["origin"] = Json::string(value)
}
None => ()
}
match format {
Some(value) =>
if !(is_supported_screenshot_format(value)) {
return Err(
invalid_argument_error(
"format must be one of image/png|image/jpeg|png|jpeg",
),
)
} else {
params["format"] = Json::string(value)
}
None => ()
}
match quality {
Some(value) =>
if value < 0 || value > 100 {
return Err(invalid_argument_error("quality must be between 0 and 100"))
} else {
params["quality"] = Json::number(value.to_double())
}
None => ()
}
match clip {
Some(value) => params["clip"] = value
None => ()
}
let result = self.dispatch(
"browsingContext",
"captureScreenshot",
params=Some(Json::object(params)),
)
match result {
Ok(json) =>
match get_string_field(json, "data") {
Some(data) => Ok(data)
None => Err(ParseError(message="Missing data field"))
}
Err(e) => Err(e)
}
}
///|
/// browsingContext.locateNodes
pub async fn BidiClient::browsing_context_locate_nodes(
self : BidiClient,
context_id : String,
locator : Json,
max_node_count? : Int,
serialization_options? : SerializationOptions,
) -> Result[Json, ClientError] {
self.locate_nodes_impl(
context_id, locator, max_node_count, serialization_options,
)
}
///|
/// browsingContext.locateNodes (typed)
pub async fn BidiClient::browsing_context_locate_nodes_typed(
self : BidiClient,
context_id : String,
locator : Locator,
max_node_count? : Int,
serialization_options? : SerializationOptions,
) -> Result[Json, ClientError] {
self.locate_nodes_impl(
context_id,
locator.to_json(),
max_node_count,
serialization_options,
)
}
///|
/// browsingContext.locateNodes (typed refs)
pub async fn BidiClient::browsing_context_locate_nodes_typed_refs(
self : BidiClient,
context_id : String,
locator : Locator,
max_node_count? : Int,
serialization_options? : SerializationOptions,
) -> Result[Array[NodeReference], ClientError] {
let result = self.locate_nodes_impl(
context_id,
locator.to_json(),
max_node_count,
serialization_options,
)
match result {
Ok(json) => parse_node_references(json)
Err(e) => Err(e)
}
}
///|
async fn BidiClient::locate_nodes_impl(
self : BidiClient,
context_id : String,
locator : Json,
max_node_count : Int?,
serialization_options : SerializationOptions?,
) -> Result[Json, ClientError] {
match validate_non_empty_string(context_id, "context") {
Ok(_) => ()
Err(e) => return Err(e)
}
match validate_locator(locator) {
Ok(_) => ()
Err(e) => return Err(e)
}
let params : Map[String, Json] = {}
params["context"] = Json::string(context_id)
params["locator"] = locator
match max_node_count {
Some(count) =>
if count <= 0 {
return Err(
invalid_argument_error("maxNodeCount must be greater than 0"),
)
} else {
params["maxNodeCount"] = Json::number(count.to_double())
}
None => ()
}
match serialization_options {
Some(opts) =>
match validate_serialization_options(opts) {
Ok(_) => params["serializationOptions"] = opts.to_json()
Err(e) => return Err(e)
}
None => ()
}
self.dispatch(
"browsingContext",
"locateNodes",
params=Some(Json::object(params)),
)
}
///|
fn parse_node_references(
result : Json,
) -> Result[Array[NodeReference], ClientError] {
let nodes = match get_array_field(result, "nodes") {
Some(values) => values
None => return Err(ParseError(message="Missing nodes field"))
}
let references : Array[NodeReference] = []
for node in nodes {
let handle = get_string_field(node, "handle")
let shared_id = get_string_field(node, "sharedId")
if handle == None && shared_id == None {
return Err(
ParseError(message="Node reference must include handle or sharedId"),
)
}
references.push({ handle, shared_id })
}
Ok(references)
}
///|
/// script.evaluate
pub async fn BidiClient::script_evaluate(
self : BidiClient,
expression : String,
context_id : String,
await_promise? : Bool = true,
result_ownership? : ResultOwnership,
serialization_options? : SerializationOptions,
user_activation? : Bool,
) -> Result[Json, ClientError] {
match validate_non_empty_string(expression, "expression") {
Ok(_) => ()
Err(e) => return Err(e)
}
match validate_non_empty_string(context_id, "context") {
Ok(_) => ()
Err(e) => return Err(e)
}
let params : Map[String, Json] = {}
params["expression"] = Json::string(expression)
params["target"] = Json::object({ "context": Json::string(context_id) })
params["awaitPromise"] = Json::boolean(await_promise)
match result_ownership {
Some(ownership) =>
params["resultOwnership"] = Json::string(ownership.to_string())
None => ()
}
match serialization_options {
Some(opts) =>
match validate_serialization_options(opts) {
Ok(_) => params["serializationOptions"] = opts.to_json()
Err(e) => return Err(e)
}
None => ()
}
match user_activation {
Some(active) => params["userActivation"] = Json::boolean(active)
None => ()
}
self.dispatch("script", "evaluate", params=Some(Json::object(params)))
}
///|
/// script.callFunction
pub async fn BidiClient::script_call_function(
self : BidiClient,
function_declaration : String,
context_id : String,
arguments? : Array[Json] = [],
await_promise? : Bool = true,
result_ownership? : ResultOwnership,
serialization_options? : SerializationOptions,
user_activation? : Bool,
this_value? : Json,
) -> Result[Json, ClientError] {
self.call_function_impl(
function_declaration, context_id, arguments, await_promise, result_ownership,
serialization_options, user_activation, this_value,
)
}
///|
/// script.callFunction (typed arguments)
pub async fn BidiClient::script_call_function_typed(
self : BidiClient,
function_declaration : String,
context_id : String,
arguments? : Array[RemoteValue] = [],
await_promise? : Bool = true,
result_ownership? : ResultOwnership,
serialization_options? : SerializationOptions,
user_activation? : Bool,
this_value? : RemoteValue,
) -> Result[Json, ClientError] {
let args_json : Array[Json] = []
for arg in arguments {
args_json.push(arg.to_json())
}
let this_value_json = match this_value {
Some(value) => Some(value.to_json())
None => None
}
self.call_function_impl(
function_declaration, context_id, args_json, await_promise, result_ownership,
serialization_options, user_activation, this_value_json,
)
}
///|
async fn BidiClient::call_function_impl(
self : BidiClient,
function_declaration : String,
context_id : String,
arguments : Array[Json],
await_promise : Bool,
result_ownership : ResultOwnership?,
serialization_options : SerializationOptions?,
user_activation : Bool?,
this_value : Json?,
) -> Result[Json, ClientError] {
match validate_non_empty_string(function_declaration, "functionDeclaration") {
Ok(_) => ()
Err(e) => return Err(e)
}
match validate_non_empty_string(context_id, "context") {
Ok(_) => ()
Err(e) => return Err(e)
}
let params : Map[String, Json] = {}
params["functionDeclaration"] = Json::string(function_declaration)
params["target"] = Json::object({ "context": Json::string(context_id) })
params["arguments"] = Json::array(arguments)
params["awaitPromise"] = Json::boolean(await_promise)
match result_ownership {
Some(ownership) =>
params["resultOwnership"] = Json::string(ownership.to_string())
None => ()
}
match serialization_options {
Some(opts) =>
match validate_serialization_options(opts) {
Ok(_) => params["serializationOptions"] = opts.to_json()
Err(e) => return Err(e)
}
None => ()
}
match user_activation {
Some(active) => params["userActivation"] = Json::boolean(active)
None => ()
}
match this_value {
Some(value) => params["this"] = value
None => ()
}
self.dispatch("script", "callFunction", params=Some(Json::object(params)))
}
///|
/// script.addPreloadScript
pub async fn BidiClient::script_add_preload_script(
self : BidiClient,
function_declaration : String,
arguments? : Array[RemoteValue] = [],
contexts? : Array[String],
user_contexts? : Array[String],
sandbox? : String,
) -> Result[String, ClientError] {
match validate_non_empty_string(function_declaration, "functionDeclaration") {
Ok(_) => ()
Err(e) => return Err(e)
}
let args_json : Array[Json] = []
for arg in arguments {
args_json.push(arg.to_json())
}
let params : Map[String, Json] = {}
params["functionDeclaration"] = Json::string(function_declaration)
params["arguments"] = Json::array(args_json)
match contexts {
Some(values) => {
match validate_context_ids(values, "contexts") {
Ok(_) => ()
Err(e) => return Err(e)
}
let items : Array[Json] = []
for value in values {
items.push(Json::string(value))
}
params["contexts"] = Json::array(items)
}
None => ()
}
match user_contexts {
Some(values) => {
match validate_context_ids(values, "userContexts") {
Ok(_) => ()
Err(e) => return Err(e)
}
let items : Array[Json] = []
for value in values {
items.push(Json::string(value))
}
params["userContexts"] = Json::array(items)
}
None => ()
}
match sandbox {
Some(value) =>
if value.is_empty() {
return Err(invalid_argument_error("sandbox must not be empty"))
} else {
params["sandbox"] = Json::string(value)
}
None => ()
}
let result = self.dispatch(
"script",
"addPreloadScript",
params=Some(Json::object(params)),
)
match result {
Ok(json) =>
match get_string_field(json, "script") {
Some(script_id) => Ok(script_id)
None => Err(ParseError(message="Missing script field"))
}
Err(e) => Err(e)
}
}
///|
/// script.removePreloadScript
pub async fn BidiClient::script_remove_preload_script(
self : BidiClient,
script_id : String,
) -> Result[Unit, ClientError] {
match validate_non_empty_string(script_id, "script") {
Ok(_) => ()
Err(e) => return Err(e)
}
let params : Map[String, Json] = {}
params["script"] = Json::string(script_id)
match
self.dispatch(
"script",
"removePreloadScript",
params=Some(Json::object(params)),
) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// script.disown
pub async fn BidiClient::script_disown(
self : BidiClient,
handles : Array[String],
context_id? : String,
sandbox? : String,
) -> Result[Unit, ClientError] {
if handles.length() == 0 {
return Err(invalid_argument_error("handles must not be empty"))
}
let target : Map[String, Json] = {}
match context_id {
Some(value) =>
if value.is_empty() {
return Err(invalid_argument_error("context must not be empty"))
} else {
target["context"] = Json::string(value)
}
None => return Err(invalid_argument_error("context is required"))
}
match sandbox {
Some(value) =>
if value.is_empty() {
return Err(invalid_argument_error("sandbox must not be empty"))
} else {
target["sandbox"] = Json::string(value)
}
None => ()
}
let handle_values : Array[Json] = []
for handle in handles {
if handle.is_empty() {
return Err(
invalid_argument_error("handles must not contain empty values"),
)
}
handle_values.push(Json::string(handle))
}
let params : Map[String, Json] = {}
params["handles"] = Json::array(handle_values)
params["target"] = Json::object(target)
match self.dispatch("script", "disown", params=Some(Json::object(params))) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// input.performActions
pub async fn BidiClient::input_perform_actions(
self : BidiClient,
actions : Array[Json],
context_id? : String,
) -> Result[Unit, ClientError] {
if actions.length() == 0 {
return Err(invalid_argument_error("actions must not be empty"))
}
let params : Map[String, Json] = {}
params["actions"] = Json::array(actions)
match context_id {
Some(ctx) =>
if ctx.is_empty() {
return Err(invalid_argument_error("context must not be empty"))
} else {
params["context"] = Json::string(ctx)
}
None => ()
}
match
self.dispatch("input", "performActions", params=Some(Json::object(params))) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// input.performActions (typed)
pub async fn BidiClient::input_perform_actions_typed(
self : BidiClient,
actions : Array[ActionSequence],
context_id? : String,
) -> Result[Unit, ClientError] {
let actions_json : Array[Json] = []
for action in actions {
actions_json.push(action.to_json())
}
match context_id {
Some(ctx) => self.input_perform_actions(actions_json, context_id=ctx)
None => self.input_perform_actions(actions_json)
}
}
///|
/// input.releaseActions
pub async fn BidiClient::input_release_actions(
self : BidiClient,
context_id? : String,
) -> Result[Unit, ClientError] {
let params : Map[String, Json] = {}
match context_id {
Some(ctx) =>
if ctx.is_empty() {
return Err(invalid_argument_error("context must not be empty"))
} else {
params["context"] = Json::string(ctx)
}
None => ()
}
match
self.dispatch("input", "releaseActions", params=Some(Json::object(params))) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// network.setCacheBehavior
pub async fn BidiClient::network_set_cache_behavior(
self : BidiClient,
cache_behavior : String,
contexts? : Array[String],
) -> Result[Unit, ClientError] {
if !(is_supported_cache_behavior(cache_behavior)) {
return Err(
invalid_argument_error("cacheBehavior must be one of default|bypass"),
)
}
let params : Map[String, Json] = {}
params["cacheBehavior"] = Json::string(cache_behavior)
match contexts {
Some(items) => {
match validate_context_ids(items, "contexts") {
Ok(_) => ()
Err(e) => return Err(e)
}
let json_contexts : Array[Json] = []
for ctx in items {
json_contexts.push(Json::string(ctx))
}
params["contexts"] = Json::array(json_contexts)
}
None => ()
}
match
self.dispatch(
"network",
"setCacheBehavior",
params=Some(Json::object(params)),
) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// network.setCacheBehavior (typed)
pub async fn BidiClient::network_set_cache_behavior_typed(
self : BidiClient,
cache_behavior : CacheBehavior,
contexts? : Array[String],
) -> Result[Unit, ClientError] {
match contexts {
Some(list) =>
self.network_set_cache_behavior(cache_behavior.to_string(), contexts=list)
None => self.network_set_cache_behavior(cache_behavior.to_string())
}
}
///|
/// network.addDataCollector
pub async fn BidiClient::network_add_data_collector(
self : BidiClient,
) -> Result[Unit, ClientError] {
match self.dispatch("network", "addDataCollector") {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// network.addDataCollector (typed)
pub async fn BidiClient::network_add_data_collector_typed(
self : BidiClient,
collector : NetworkDataCollector,
) -> Result[Unit, ClientError] {
if collector.data_types.length() == 0 {
return Err(invalid_argument_error("dataTypes must not be empty"))
}
for data_type in collector.data_types {
if !(is_supported_data_type(data_type)) {
return Err(invalid_argument_error("Invalid data type: " + data_type))
}
}
match collector.contexts {
Some(contexts) =>
match validate_context_ids(contexts, "contexts") {
Ok(_) => ()
Err(e) => return Err(e)
}
None => ()
}
match
self.dispatch(
"network",
"addDataCollector",
params=Some(collector.to_json()),
) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
/// Wait for response with matching id
async fn BidiClient::wait_response(
self : BidiClient,
id : Int,
) -> Result[BidiResponse, ClientError] {
match self.pending.get(id) {
Some(resp) => {
self.pending.remove(id)
return Ok(resp)
}
None => ()
}
let incoming = match self.transport.recv() {
Ok(raw) =>
match parse_incoming(raw) {
Ok(msg) => msg
Err(e) => return Err(ParseError(message=e))
}
Err(e) => return Err(Transport(e))
}
match incoming {
Event(evt) => {
self.dispatch_event(evt)
self.wait_response(id)
}
Response(resp) =>
if resp.id == id {
Ok(resp)
} else {
self.pending[resp.id] = resp
self.wait_response(id)
}
}
}
///|
/// Dispatch event to handlers
fn BidiClient::dispatch_event(self : BidiClient, evt : BidiEvent) -> Unit {
if !(self.is_event_subscribed(evt)) {
return ()
}
for handler in self.event_handlers {
handler(evt)
}
}
///|
fn BidiClient::is_event_subscribed(self : BidiClient, evt : BidiEvent) -> Bool {
if !(self.has_subscription_filter) {
return true
}
if self.global_subscriptions.contains(evt.event_method) {
return true
}
match get_string_field(evt.params, "context") {
Some(context_id) =>
match self.context_subscriptions.get(context_id) {
Some(events) => events.contains(evt.event_method)
None => false
}
None => false
}
}
///|
fn BidiClient::apply_subscribe(
self : BidiClient,
events : Array[String],
contexts : Array[String]?,
) -> Unit {
self.has_subscription_filter = true
match contexts {
Some(context_ids) =>
for context_id in context_ids {
let context_events = match self.context_subscriptions.get(context_id) {
Some(existing) => existing
None => {
let created : Map[String, Bool] = {}
self.context_subscriptions[context_id] = created
created
}
}
for event_name in events {
context_events[event_name] = true
}
}
None =>
for event_name in events {
self.global_subscriptions[event_name] = true
}
}
}
///|
fn BidiClient::apply_unsubscribe(
self : BidiClient,
events : Array[String],
contexts : Array[String]?,
) -> Unit {
match contexts {
Some(context_ids) =>
for context_id in context_ids {
match self.context_subscriptions.get(context_id) {
Some(context_events) => {
for event_name in events {
context_events.remove(event_name)
}
if context_events.is_empty() {
self.context_subscriptions.remove(context_id)
}
}
None => ()
}
}
None =>
for event_name in events {
self.global_subscriptions.remove(event_name)
}
}
self.has_subscription_filter = true
}
///|
fn is_valid_navigation_wait(wait : String) -> Bool {
wait == "none" || wait == "interactive" || wait == "complete"
}
///|
fn is_supported_screenshot_format(format : String) -> Bool {
format == "image/png" ||
format == "image/jpeg" ||
format == "png" ||
format == "jpeg"
}
///|
fn is_supported_cache_behavior(cache_behavior : String) -> Bool {
cache_behavior == "default" || cache_behavior == "bypass"
}
///|
fn is_supported_data_type(data_type : String) -> Bool {
data_type == "request" || data_type == "response"
}
///|
fn validate_non_empty_string(
value : String,
field_name : String,
) -> Result[Unit, ClientError] {
if value.is_empty() {
Err(invalid_argument_error(field_name + " must not be empty"))
} else {
Ok(())
}
}
///|
fn validate_serialization_options(
options : SerializationOptions,
) -> Result[Unit, ClientError] {
match options.max_depth {
Some(depth) =>
if depth < 0 {
return Err(
invalid_argument_error(
"serializationOptions.maxDepth must be non-negative",
),
)
} else {
()
}
None => ()
}
match options.max_object_depth {
Some(depth) =>
if depth < 0 {
return Err(
invalid_argument_error(
"serializationOptions.maxObjectDepth must be non-negative",
),
)
} else {
()
}
None => ()
}
match options.include_shadow_tree {
Some(value) =>
if value != "none" && value != "open" && value != "all" {
return Err(
invalid_argument_error(
"serializationOptions.includeShadowTree must be one of none|open|all",
),
)
} else {
()
}
None => ()
}
Ok(())
}
///|
fn validate_locator(locator : Json) -> Result[Unit, ClientError] {
let locator_type = match get_string_field(locator, "type") {
Some(value) => value
None => return Err(invalid_argument_error("locator.type is required"))
}
if locator_type == "css" || locator_type == "xpath" {
match get_string_field(locator, "value") {
Some(value) =>
if value.is_empty() {
Err(
invalid_argument_error(
"locator.value must be a non-empty string for " +
locator_type +
" locator",
),
)
} else {
Ok(())
}
None =>
Err(
invalid_argument_error(
"locator.value must be a non-empty string for " +
locator_type +
" locator",
),
)
}
} else {
Ok(())
}
}
///|
fn invalid_argument_error(message : String) -> ClientError {
InvalidArgument(message="invalid argument: " + message)
}
///|
fn unknown_command_error(domain : String, command : String) -> ClientError {
InvalidArgument(message="unknown command: " + domain + "." + command)
}
///|
fn validate_subscription_request(
events : Array[String],
contexts : Array[String]?,
) -> Result[Unit, ClientError] {
if events.length() == 0 {
return Err(invalid_argument_error("events must not be empty"))
}
for event_name in events {
if !(is_supported_event_name(event_name)) {
return Err(invalid_argument_error("Invalid event name: " + event_name))
}
}
match contexts {
Some(context_ids) => validate_context_ids(context_ids, "contexts")
None => Ok(())
}
}
///|
fn validate_context_ids(
context_ids : Array[String],
field_name : String,
) -> Result[Unit, ClientError] {
if context_ids.length() == 0 {
return Err(invalid_argument_error(field_name + " must not be empty"))
}
for context_id in context_ids {
if context_id.is_empty() {
return Err(
invalid_argument_error(field_name + " must not contain empty values"),
)
}
}
Ok(())
}
///|
fn is_supported_event_name(event_name : String) -> Bool {
let domains = [
"session", "browser", "browsingContext", "script", "input", "network", "log",
]
for domain in domains {
let prefix = domain + "."
if event_name.has_prefix(prefix) && event_name.length() > prefix.length() {
return true
}
}
false
}
///|
fn is_supported_domain_command(domain : String, command : String) -> Bool {
match domain {
"session" =>
command == "status" ||
command == "new" ||
command == "subscribe" ||
command == "unsubscribe" ||
command == "end"
"browser" =>
command == "getUserContexts" ||
command == "createUserContext" ||
command == "removeUserContext" ||
command == "close"
"browsingContext" =>
command == "create" ||
command == "reload" ||
command == "close" ||
command == "getTree" ||
command == "navigate" ||
command == "captureScreenshot" ||
command == "locateNodes"
"script" =>
command == "evaluate" ||
command == "callFunction" ||
command == "addPreloadScript" ||
command == "removePreloadScript" ||
command == "disown"
"input" => command == "performActions" || command == "releaseActions"
"network" => command == "setCacheBehavior" || command == "addDataCollector"
_ => false
}
}