///|
/// BiDi event routing
///|
fn register_bidi_event_router(state_ref : Ref[BrowserState]) -> Unit {
let client = state_ref.val.client
client.on_event(fn(evt : @bidi.BidiEvent) {
dispatch_bidi_event(state_ref, evt)
})
}
///|
fn dispatch_bidi_event(
state_ref : Ref[BrowserState],
evt : @bidi.BidiEvent,
) -> Unit {
match evt.event_method {
"browsingContext.load" =>
handle_navigation_event(state_ref, "browsingContext.load", evt.params)
"browsingContext.domContentLoaded" =>
handle_navigation_event(
state_ref,
"browsingContext.domContentLoaded",
evt.params,
)
"network.beforeRequestSent" => handle_network_request(state_ref, evt.params)
"network.responseStarted" =>
handle_network_response(state_ref, "started", evt.params)
"network.responseCompleted" =>
handle_network_response(state_ref, "completed", evt.params)
"log.entryAdded" => handle_log_entry(state_ref, evt.params)
_ => ()
}
}
///|
fn handle_navigation_event(
state_ref : Ref[BrowserState],
event_name : String,
params : Json,
) -> Unit {
let context_id = event_context_id(params)
let url = event_get_string(params, "url")
match context_id {
Some(ctx) => {
let evt : NavigationEvent = { event_name, url: url.unwrap_or("") }
match find_page_state(state_ref.val, ctx) {
Some(page_state) => {
page_state.val.navigation_events.push(evt)
match url {
Some(u) => page_state.val.url = u
None => ()
}
}
None => ()
}
}
None => ()
}
}
///|
fn handle_network_request(state_ref : Ref[BrowserState], params : Json) -> Unit {
let context_id = event_context_id(params)
match (context_id, event_get_object(params, "request")) {
(Some(ctx), Some(req_json)) =>
match request_from_json(req_json) {
Some(req) =>
match find_page_state(state_ref.val, ctx) {
Some(page_state) => {
page_state.val.request_events.push(req)
dispatch_network_event(page_state, NetworkEvent::Request(req))
}
None => ()
}
None => ()
}
_ => ()
}
}
///|
fn handle_network_response(
state_ref : Ref[BrowserState],
stage : String,
params : Json,
) -> Unit {
let context_id = event_context_id(params)
match (context_id, event_get_object(params, "response")) {
(Some(ctx), Some(resp_json)) =>
match response_from_json(resp_json) {
Some(resp) =>
match find_page_state(state_ref.val, ctx) {
Some(page_state) =>
if stage == "started" {
page_state.val.response_events.push(resp)
dispatch_network_event(page_state, NetworkEvent::Response(resp))
} else {
page_state.val.response_completed_events.push(resp)
dispatch_network_event(
page_state,
NetworkEvent::ResponseCompleted(resp),
)
}
None => ()
}
None => ()
}
_ => ()
}
}
///|
fn dispatch_network_event(
page_state : Ref[PageState],
event : NetworkEvent,
) -> Unit {
for handler in page_state.val.network_listeners {
handler(event)
}
}
///|
fn handle_log_entry(state_ref : Ref[BrowserState], params : Json) -> Unit {
let context_id = event_context_id(params)
let level = event_get_string(params, "level").unwrap_or("info")
let text = event_get_string(params, "text").unwrap_or("")
let source = event_get_string(params, "source").unwrap_or("")
match context_id {
Some(ctx) =>
match find_page_state(state_ref.val, ctx) {
Some(page_state) =>
page_state.val.console_messages.push({ level, text, source })
None => ()
}
None => ()
}
}
///|
fn find_page_state(
state : BrowserState,
context_id : String,
) -> Ref[PageState]? {
for ctx_state in state.contexts {
for page_state in ctx_state.val.pages {
if page_state.val.context_id == context_id {
return Some(page_state)
}
}
}
match state.default_context {
Some(ctx_ref) =>
for page_state in ctx_ref.val.state.val.pages {
if page_state.val.context_id == context_id {
return Some(page_state)
}
}
None => ()
}
None
}
///|
fn event_context_id(params : Json) -> String? {
match event_get_string(params, "context") {
Some(ctx) => Some(ctx)
None => event_get_string(params, "browsingContext")
}
}
///|
fn request_from_json(json : Json) -> Request? {
match (event_get_string(json, "url"), event_get_string(json, "method")) {
(Some(url), Some(method_)) => {
let headers = match event_get_array(json, "headers") {
Some(items) => headers_from_array(items)
None => Headers::empty()
}
Some({ url_: url, method_, headers_: headers })
}
_ => None
}
}
///|
fn response_from_json(json : Json) -> Response? {
let url = event_get_string(json, "url")
let status = event_get_number(json, "status")
let status_text = event_get_string(json, "status_text")
match (url, status) {
(Some(u), Some(code)) => {
let headers = match event_get_array(json, "headers") {
Some(items) => headers_from_array(items)
None => Headers::empty()
}
Some({
url_: u,
status_: code.to_int(),
status_text_: status_text.unwrap_or(""),
headers_: headers,
from_cache_: false,
from_service_worker_: false,
})
}
_ => None
}
}
///|
fn headers_from_array(items : Array[Json]) -> Headers {
let pairs : Array[(String, String)] = []
for item in items {
match (event_get_string(item, "name"), event_get_string(item, "value")) {
(Some(name), Some(value)) => pairs.push((name, value))
_ => ()
}
}
Headers::from_pairs(pairs)
}
///|
fn event_get_string(json : Json, key : String) -> String? {
match json {
Object(map) =>
match map.get(key) {
Some(String(s)) => Some(s)
_ => None
}
_ => None
}
}
///|
fn event_get_number(json : Json, key : String) -> Double? {
match json {
Object(map) =>
match map.get(key) {
Some(Number(value, ..)) => Some(value)
_ => None
}
_ => None
}
}
///|
fn event_get_object(json : Json, key : String) -> Json? {
match json {
Object(map) =>
match map.get(key) {
Some(Object(obj)) => Some(Json::object(obj))
_ => None
}
_ => None
}
}
///|
fn event_get_array(json : Json, key : String) -> Array[Json]? {
match json {
Object(map) =>
match map.get(key) {
Some(Array(items)) => Some(items)
_ => None
}
_ => None
}
}