///|
extern "js" fn js_utf8_byte_length(value : String) -> Int =
#| (value) => new TextEncoder().encode(String(value ?? "")).length
///|
extern "js" fn js_base64_byte_length(value : String) -> Int =
#| (value) => {
#| try {
#| return Uint8Array.from(atob(String(value ?? "")), (c) => c.charCodeAt(0)).length;
#| } catch (_) {
#| return 0;
#| }
#| }
///|
extern "js" fn js_decode_query_component(value : String) -> String =
#| (value) => {
#| try {
#| const text = value === null || value === undefined ? "" : String(value);
#| return decodeURIComponent(text.replace(/\+/g, " "));
#| } catch (_) {
#| return value === null || value === undefined ? "" : String(value);
#| }
#| }
///|
priv struct SyntheticNetworkCookieOverride {
enabled : Bool
cookies : Array[Json]
}
///|
priv struct SyntheticNetworkEventInput {
context_id : String
url : String
http_method : String
request_token : String
request_headers : Array[Json]
request_value : Json?
request_cookies : Array[Json]
request_cookies_override : Bool
intercepts : Array[String]
redirect_count : Int
navigation : String?
destination : String
initiator_type : String?
}
///|
priv struct SyntheticNetworkResponseOverrides {
from_cache : Bool
headers : Array[Json]
mime_type : String
protocol : String
status : Int
status_text : String
auth_challenges : Json?
}
///|
priv struct SyntheticNetworkContinueCredentials {
username : String
password : String
}
///|
priv struct SyntheticNetworkSetCookieEntries {
headers : Array[Json]
cookies : Array[StoredCookie]
}
///|
fn BidiProtocol::handle_network_build_before_request_sent_event(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let input = match
self.normalize_synthetic_network_event_input(request_id, params) {
Some(input) => input
None => return
}
let event = match
self.build_synthetic_before_request_sent_event_json(request_id, input) {
Some(event) => event
None => return
}
self.send_success(request_id, Some(make_object({ "event": event })))
}
///|
fn BidiProtocol::handle_network_build_response_event(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let input = match
self.normalize_synthetic_network_event_input(request_id, params) {
Some(input) => input
None => return
}
let response_overrides = match
self.normalize_synthetic_network_response_overrides(
request_id,
get_param_raw(params, "responseOverrides"),
) {
Some(response_overrides) => response_overrides
None => return
}
let event = match
self.build_synthetic_response_event_json(
request_id, input, response_overrides,
) {
Some(event) => event
None => return
}
self.send_success(request_id, Some(make_object({ "event": event })))
}
///|
fn BidiProtocol::handle_network_build_fetch_error_event(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let input = match
self.normalize_synthetic_network_event_input(request_id, params) {
Some(input) => input
None => return
}
let error_text = match get_param_raw(params, "errorText") {
None | Some(Null) => "Request failed"
Some(String(text)) => text
Some(_) => {
self.send_error(
request_id, "invalid argument", "errorText must be a string",
)
return
}
}
let event = match
self.build_synthetic_fetch_error_event_json(request_id, input, error_text) {
Some(event) => event
None => return
}
self.send_success(request_id, Some(make_object({ "event": event })))
}
///|
fn BidiProtocol::handle_network_build_headers_echo_payload(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let context_id = match map.get("context") {
Some(String(context_id)) => context_id
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return
}
}
if !self.manager.has_session(context_id) {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + context_id,
)
return
}
let request_headers = match
self.normalize_synthetic_network_header_entries(
request_id,
get_map_field_with_alias(map, "requestHeaders", "request_headers"),
) {
Some(request_headers) => request_headers
None => return
}
let payload = self.build_network_headers_echo_payload(
context_id, request_headers,
)
self.send_success(
request_id,
Some(make_object({ "payload": Json::string(payload) })),
)
}
///|
fn BidiProtocol::handle_network_prepare_navigation_request(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let context_id = match map.get("context") {
Some(String(context_id)) => context_id
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return
}
}
if !self.manager.has_session(context_id) {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + context_id,
)
return
}
let url = match map.get("url") {
Some(String(url)) => url
_ => {
self.send_error(request_id, "invalid argument", "url must be a string")
return
}
}
match self.prepare_navigation_request_internal(request_id, context_id, url) {
Some(result) => self.send_success(request_id, Some(result))
None => ()
}
}
///|
fn BidiProtocol::prepare_navigation_request_internal(
self : BidiProtocol,
request_id : Int,
context_id : String,
url : String,
) -> Json? {
let event_name = "network.beforeRequestSent"
if !self.is_subscribed_for_context(event_name, context_id) {
return Some(make_object({ "blocked": Json::boolean(false) }))
}
let intercepts = self.resolve_matching_network_intercepts_internal(
context_id, "beforeRequestSent", url,
)
if intercepts.length() == 0 {
return Some(make_object({ "blocked": Json::boolean(false) }))
}
let navigation_id = self.next_navigation_id.to_string()
self.next_navigation_id += 1
self.remember_requested_navigation_url(context_id, url)
self.register_in_flight_navigation(
context_id,
url,
navigation_id,
Some(request_id),
)
let input : SyntheticNetworkEventInput = {
context_id,
url,
http_method: "GET",
request_token: self.next_synthetic_network_request_id(),
request_headers: [],
request_value: None,
request_cookies: [],
request_cookies_override: false,
intercepts,
redirect_count: 0,
navigation: Some(navigation_id),
destination: "document",
initiator_type: None,
}
let event = match
self.build_synthetic_before_request_sent_event_json(request_id, input) {
Some(event) => event
None => return None
}
self.outbox.push(Event({ event_method: event_name, params: event }))
let blocked_request : Map[String, Json] = {}
blocked_request["phase"] = Json::string("beforeRequestSent")
blocked_request["context_id"] = Json::string(context_id)
blocked_request["url"] = Json::string(url)
blocked_request["method"] = Json::string("GET")
blocked_request["request_headers"] = make_object({})
blocked_request["redirect_count"] = Json::number(0.0)
blocked_request["navigation"] = Json::string(navigation_id)
blocked_request["destination"] = Json::string("document")
blocked_request["initiator_type"] = Json::null()
blocked_request["is_navigation"] = Json::boolean(true)
self.remember_network_blocked_request(input.request_token, blocked_request)
Some(
make_object({
"blocked": Json::boolean(true),
"navigation": Json::string(navigation_id),
"url": Json::string(url),
}),
)
}
///|
fn BidiProtocol::handle_network_fail_blocked_request(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let blocked_request_id = match
self.synthetic_network_request_lookup_id(request_id, params) {
Some(blocked_request_id) => blocked_request_id
None => return
}
let blocked_request = match
self.get_network_blocked_request(blocked_request_id) {
Some(blocked_request) => blocked_request
None => {
self.send_error(request_id, "no such request", "Unknown request")
return
}
}
match blocked_request.get("phase") {
Some(String("authRequired")) => {
self.send_error(
request_id, "invalid argument", "authRequired requests cannot be failed",
)
return
}
_ => ()
}
let input = match
self.synthetic_network_event_input_from_blocked_request(
request_id, blocked_request_id, blocked_request,
) {
Some(input) => input
None => return
}
let error_text = match get_param_raw(params, "errorText") {
None | Some(Null) => "Request failed"
Some(String(text)) => text
Some(_) => {
self.send_error(
request_id, "invalid argument", "errorText must be a string",
)
return
}
}
let event = match
self.build_synthetic_fetch_error_event_json(request_id, input, error_text) {
Some(event) => event
None => return
}
let is_navigation = match
get_map_field_with_alias(blocked_request, "isNavigation", "is_navigation") {
Some(True) => true
_ => false
}
self.forget_network_blocked_request(blocked_request_id)
self.send_success(request_id, Some(make_object({ "event": event })))
self.emit_synthetic_network_event_if_subscribed(
"network.fetchError",
Some(event),
)
if is_navigation {
self.fail_pending_navigation_response(
input.context_id,
input.navigation,
"Navigation failed: " + error_text,
)
}
}
///|
fn BidiProtocol::handle_network_continue_auth_request(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let blocked_request_id = match map.get("request") {
Some(String(blocked_request_id)) => blocked_request_id
_ => {
self.send_error(
request_id, "invalid argument", "request must be a string",
)
return
}
}
let action = match map.get("action") {
Some(String(action)) => action
Some(_) => {
self.send_error(request_id, "invalid argument", "action must be a string")
return
}
None => {
self.send_error(request_id, "invalid argument", "action must be a string")
return
}
}
if action == "provideCredentials" {
if !map.contains("credentials") {
self.send_error(
request_id, "invalid argument", "credentials must be an object",
)
return
}
let forwarded : Map[String, Json] = {}
forwarded["request"] = map.get("request").unwrap_or(Json::null())
forwarded["mode"] = Json::string("continueResponse")
forwarded["credentials"] = map.get("credentials").unwrap_or(Json::null())
self.handle_network_continue_blocked_response(
request_id,
Some(Json::object(forwarded)),
)
return
}
if action != "default" && action != "cancel" {
self.send_error(request_id, "invalid argument", "action is invalid")
return
}
let blocked_request = match
self.get_network_blocked_request(blocked_request_id) {
Some(blocked_request) => blocked_request
None => {
self.send_error(request_id, "no such request", "Unknown request")
return
}
}
match blocked_request.get("phase") {
Some(String("authRequired")) => ()
_ => {
self.send_error(
request_id, "invalid argument", "Request is not blocked at authRequired",
)
return
}
}
if action == "default" {
self.send_success(
request_id,
Some(
make_object({ "event": Json::null(), "consumed": Json::boolean(false) }),
),
)
return
}
let input = match
self.synthetic_network_event_input_from_blocked_request(
request_id, blocked_request_id, blocked_request,
) {
Some(input) => input
None => return
}
let resolved_overrides = self.resolve_synthetic_response_overrides_internal(
input.context_id,
input.url,
input.http_method,
input.request_headers,
false,
)
let status = synthetic_network_blocked_response_status(blocked_request)
let response_overrides : SyntheticNetworkResponseOverrides = {
from_cache: resolved_overrides.from_cache,
headers: synthetic_network_blocked_response_headers(blocked_request),
mime_type: resolved_overrides.mime_type,
protocol: resolved_overrides.protocol,
status,
status_text: synthetic_network_blocked_response_status_text(
blocked_request, status,
),
auth_challenges: Some(Json::array([])),
}
let event = match
self.build_synthetic_response_event_json(
request_id, input, response_overrides,
) {
Some(event) => event
None => return
}
self.forget_network_blocked_request(blocked_request_id)
self.send_success(
request_id,
Some(make_object({ "event": event, "consumed": Json::boolean(true) })),
)
self.emit_synthetic_network_event_if_subscribed(
"network.responseCompleted",
Some(event),
)
}
///|
fn BidiProtocol::handle_network_continue_blocked_request(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let blocked_request_id = match map.get("request") {
Some(String(blocked_request_id)) => blocked_request_id
_ => {
self.send_error(
request_id, "invalid argument", "request must be a string",
)
return
}
}
let blocked_request = match
self.get_network_blocked_request(blocked_request_id) {
Some(blocked_request) => blocked_request
None => {
self.send_error(request_id, "no such request", "Unknown request")
return
}
}
match blocked_request.get("phase") {
Some(String("beforeRequestSent")) => ()
_ => {
self.send_error(
request_id, "invalid argument", "Request is not blocked at beforeRequestSent",
)
return
}
}
let base_input = match
self.synthetic_network_event_input_from_blocked_request(
request_id, blocked_request_id, blocked_request,
) {
Some(input) => input
None => return
}
let mut input = base_input
let mut synthetic_location_href : String? = None
let mut completed_navigation_url : String? = None
let is_navigation = match
get_map_field_with_alias(blocked_request, "isNavigation", "is_navigation") {
Some(True) => true
Some(False) => false
_ => false
}
match map.get("body") {
Some(raw_body) =>
match
self.normalize_synthetic_network_bytes_value(
request_id, raw_body, "body",
) {
Some(body_value) => input = { ..input, request_value: Some(body_value) }
None => return
}
None => ()
}
match map.get("method") {
Some(String(http_method)) =>
if is_valid_network_header_name_token(http_method) {
input = { ..input, http_method, }
} else {
self.send_error(request_id, "invalid argument", "method is invalid")
return
}
Some(_) => {
self.send_error(request_id, "invalid argument", "method must be a string")
return
}
None => ()
}
match map.get("url") {
Some(raw_url) =>
match self.normalize_synthetic_network_continue_url(request_id, raw_url) {
Some(url) => input = { ..input, url, }
None => return
}
None => ()
}
let has_headers = map.contains("headers")
let has_cookies = map.contains("cookies")
if has_headers {
let normalized_headers = match
self.normalize_synthetic_network_header_entries(
request_id,
map.get("headers"),
) {
Some(headers) => synthetic_network_merge_header_entries(headers)
None => return
}
input = { ..input, request_headers: normalized_headers }
}
if has_cookies {
let cookies_override = match
self.normalize_synthetic_network_cookie_entries(
request_id,
map.get("cookies"),
) {
Some(cookies_override) => cookies_override
None => return
}
input = {
..input,
request_cookies: cookies_override.cookies,
request_cookies_override: cookies_override.enabled,
}
} else if has_headers {
input = { ..input, request_cookies: [], request_cookies_override: true }
}
match map.get("navigation") {
Some(String(navigation)) =>
input = { ..input, navigation: Some(navigation) }
Some(Null) => input = { ..input, navigation: None }
Some(_) => {
self.send_error(
request_id, "invalid argument", "navigation must be a string",
)
return
}
None => ()
}
match map.get("syntheticLocationHref") {
Some(String(href)) => synthetic_location_href = Some(href)
Some(Null) | None => ()
Some(_) => {
self.send_error(
request_id, "invalid argument", "syntheticLocationHref must be a string",
)
return
}
}
let resolved_overrides = self.resolve_synthetic_response_overrides_internal(
input.context_id,
input.url,
input.http_method,
input.request_headers,
false,
)
if is_navigation {
let navigation_id = match input.navigation {
Some(navigation_id) => {
input = { ..input, navigation: Some(navigation_id) }
navigation_id
}
None => {
let generated_id = self.next_navigation_id.to_string()
self.next_navigation_id += 1
input = { ..input, navigation: Some(generated_id) }
generated_id
}
}
let original_url = match blocked_request.get("url") {
Some(String(url)) => url
_ => input.url
}
let resolved_url = self.resolve_effective_navigation_target_url(
input.context_id,
input.url,
)
self.input_synthetic_events_by_context.remove(input.context_id)
self.context_synthetic_scrolled.remove(input.context_id)
self.clear_synthetic_location_href(input.context_id)
self.abort_in_flight_navigation_unless_matching(
input.context_id,
Some(navigation_id),
"Navigation interrupted by another navigation",
)
if !is_valid_navigation_target_url(resolved_url) {
self.send_error(request_id, "invalid argument", "url is invalid")
return
}
if should_fail_navigation_with_unknown_error(resolved_url) {
self.emit_navigation_started_event(
input.context_id,
resolved_url,
navigation_id,
)
self.send_error(
request_id, "unknown error", "Navigation failed: navigation target is unreachable",
)
return
}
if self.is_iframe_navigation_blocked(input.context_id, resolved_url) {
self.emit_navigation_started_event(
input.context_id,
resolved_url,
navigation_id,
)
self.emit_navigation_failed_event(
input.context_id,
resolved_url,
navigation_id,
)
self.send_error(
request_id, "unknown error", "Navigation failed: blocked by embedding policy",
)
return
}
match self.manager.get_session(input.context_id) {
Some(session) =>
match session.navigate_to(resolved_url) {
Ok(_) => {
self.remember_requested_navigation_url(input.context_id, input.url)
self.apply_navigation_commit_state(
input.context_id,
resolved_url,
navigation_id,
)
self.register_in_flight_navigation(
input.context_id,
resolved_url,
navigation_id,
self.pending_navigation_request.get(input.context_id),
)
completed_navigation_url = Some(resolved_url)
if synthetic_location_href == None && original_url != input.url {
synthetic_location_href = Some(original_url)
}
}
Err(e) => {
self.emit_navigation_started_event(
input.context_id,
resolved_url,
navigation_id,
)
self.send_error(
request_id,
"unknown error",
"Navigation failed: " + e.message,
)
return
}
}
None => {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + input.context_id,
)
return
}
}
}
let response_overrides : SyntheticNetworkResponseOverrides = {
from_cache: resolved_overrides.from_cache,
headers: resolved_overrides.headers,
mime_type: resolved_overrides.mime_type,
protocol: resolved_overrides.protocol,
status: resolved_overrides.status,
status_text: resolved_overrides.status_text,
auth_challenges: resolved_overrides.auth_challenges,
}
let response_started_event = match
self.build_synthetic_response_event_json(
request_id, input, response_overrides,
) {
Some(event) => event
None => return
}
let response_completed_event = match
self.build_synthetic_response_event_json(
request_id, input, response_overrides,
) {
Some(event) => event
None => return
}
match synthetic_location_href {
Some(href) => self.remember_synthetic_location_href(input.context_id, href)
None => ()
}
self.forget_network_blocked_request(blocked_request_id)
self.send_continue_blocked_response_result(
request_id,
Some(response_started_event),
Some(response_completed_event),
None,
None,
true,
None,
None,
None,
)
self.emit_synthetic_network_result_events(
Some(response_started_event),
Some(response_completed_event),
None,
None,
)
match completed_navigation_url {
Some(url) => {
if !self.is_subscribed_for_context(
"browsingContext.load",
input.context_id,
) {
self.outbox.push(
Event({
event_method: "browsingContext.load",
params: synthetic_network_load_event_json(
input.context_id,
input.navigation,
url,
),
}),
)
}
self.complete_pending_navigation_response(
input.context_id,
Some(url),
input.navigation,
)
}
None => ()
}
}
///|
fn BidiProtocol::handle_network_continue_blocked_response(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let blocked_request_id = match map.get("request") {
Some(String(blocked_request_id)) => blocked_request_id
_ => {
self.send_error(
request_id, "invalid argument", "request must be a string",
)
return
}
}
let mode = match map.get("mode") {
Some(String(mode)) => mode
_ => {
self.send_error(request_id, "invalid argument", "mode must be a string")
return
}
}
if mode != "continueResponse" && mode != "provideResponse" {
self.send_error(request_id, "invalid argument", "mode is invalid")
return
}
let blocked_request = match
self.get_network_blocked_request(blocked_request_id) {
Some(blocked_request) => blocked_request
None => {
self.send_error(request_id, "no such request", "Unknown request")
return
}
}
let phase = match blocked_request.get("phase") {
Some(String(phase)) => phase
_ => {
self.send_error(request_id, "no such request", "Unknown request")
return
}
}
if mode == "continueResponse" {
if phase != "responseStarted" && phase != "authRequired" {
self.send_error(
request_id, "invalid argument", "Request is not blocked at responseStarted/authRequired",
)
return
}
} else if phase != "beforeRequestSent" &&
phase != "responseStarted" &&
phase != "authRequired" {
self.send_error(
request_id, "invalid argument", "Request is not blocked at beforeRequestSent/responseStarted/authRequired",
)
return
}
let input = match
self.synthetic_network_event_input_from_blocked_request(
request_id, blocked_request_id, blocked_request,
) {
Some(input) => input
None => return
}
let context_id = input.context_id
let has_headers = map.contains("headers")
let has_cookies = map.contains("cookies")
let has_status_code = map.contains("statusCode") ||
map.contains("status_code")
let has_reason_phrase = map.contains("reasonPhrase") ||
map.contains("reason_phrase")
let has_body = map.contains("body")
let mut response_headers = if mode == "continueResponse" {
synthetic_network_blocked_response_headers(blocked_request)
} else {
[]
}
if has_headers {
match
self.normalize_synthetic_network_header_entries(
request_id,
get_map_field_with_alias(map, "headers", "headers"),
) {
Some(headers) => response_headers = headers
None => return
}
}
let mut response_cookie_headers : Array[Json] = []
let mut response_cookies : Array[StoredCookie] = []
if has_cookies {
let normalized = match
self.normalize_synthetic_network_set_cookie_entries(
request_id,
map.get("cookies"),
input.url,
) {
Some(normalized) => normalized
None => return
}
response_cookie_headers = normalized.headers
response_cookies = normalized.cookies
}
let response_headers_with_cookies : Array[Json] = []
for header in response_headers {
response_headers_with_cookies.push(header)
}
for header in response_cookie_headers {
response_headers_with_cookies.push(header)
}
response_headers = response_headers_with_cookies
let parsed_header_cookies = synthetic_network_extract_set_cookie_entries(
response_headers,
input.url,
)
for cookie in parsed_header_cookies {
response_cookies.push(cookie)
}
let mut status_code = if mode == "continueResponse" {
synthetic_network_blocked_response_status(blocked_request)
} else {
200
}
let raw_status_code = get_map_field_with_alias(
map, "statusCode", "status_code",
)
match raw_status_code {
Some(value) =>
match json_as_safe_int(value, 0.0, 9007199254740991.0) {
Some(parsed) => status_code = parsed
None => {
self.send_error(
request_id, "invalid argument", "statusCode must be a non-negative integer",
)
return
}
}
None => ()
}
let mut reason_phrase = if mode == "continueResponse" {
synthetic_network_blocked_response_status_text(blocked_request, status_code)
} else {
"OK"
}
let raw_reason_phrase = get_map_field_with_alias(
map, "reasonPhrase", "reason_phrase",
)
match raw_reason_phrase {
Some(String(value)) => reason_phrase = value
Some(_) => {
self.send_error(
request_id, "invalid argument", "reasonPhrase must be a string",
)
return
}
None => ()
}
let mut body_text : String? = None
let mut response_body_value : Json? = None
if has_body {
match map.get("body") {
Some(raw_body) => {
let normalized_body = match
self.normalize_synthetic_network_bytes_value(
request_id, raw_body, "body",
) {
Some(value) => value
None => return
}
let text = match
self.synthetic_network_text_from_bytes_value(
request_id, raw_body, "body",
) {
Some(text) => text
None => return
}
response_body_value = Some(normalized_body)
body_text = Some(text)
}
None => {
self.send_error(
request_id, "invalid argument", "body must be an object",
)
return
}
}
}
let mut credentials : SyntheticNetworkContinueCredentials? = None
if map.contains("credentials") {
match map.get("credentials") {
Some(raw_credentials) =>
match
self.normalize_synthetic_network_continue_credentials(
request_id, raw_credentials,
) {
Some(value) => credentials = Some(value)
None => return
}
None => {
self.send_error(
request_id, "invalid argument", "credentials must be an object",
)
return
}
}
}
if phase == "authRequired" {
if mode == "provideResponse" {
let auth_required_event = match
self.build_synthetic_auth_required_event_json(
request_id, input, blocked_request,
) {
Some(event) => event
None => return
}
self.send_continue_blocked_response_result(
request_id,
None,
None,
Some(auth_required_event),
None,
false,
None,
None,
body_text,
)
self.emit_synthetic_network_result_events(
None,
None,
Some(auth_required_event),
None,
)
return
}
let should_continue = synthetic_network_credentials_match_expected(
input.url,
credentials,
)
if !should_continue {
let auth_required_event = match
self.build_synthetic_auth_required_event_json(
request_id, input, blocked_request,
) {
Some(event) => event
None => return
}
self.send_continue_blocked_response_result(
request_id,
None,
None,
Some(auth_required_event),
None,
false,
None,
None,
body_text,
)
self.emit_synthetic_network_result_events(
None,
None,
Some(auth_required_event),
None,
)
return
}
if !has_status_code {
status_code = 200
}
if !has_reason_phrase {
reason_phrase = "OK"
}
if !has_headers && !has_cookies {
response_headers = []
}
}
let resolved_overrides = self.resolve_synthetic_response_overrides_internal(
context_id,
input.url,
input.http_method,
input.request_headers,
false,
)
let final_auth_challenges : Json? = if status_code == 401 ||
status_code == 407 {
match resolved_overrides.auth_challenges {
Some(auth_challenges) => Some(auth_challenges)
None => Some(Json::array([]))
}
} else if phase == "authRequired" {
None
} else {
resolved_overrides.auth_challenges
}
let response_overrides : SyntheticNetworkResponseOverrides = {
from_cache: resolved_overrides.from_cache,
headers: response_headers,
mime_type: resolved_overrides.mime_type,
protocol: resolved_overrides.protocol,
status: status_code,
status_text: reason_phrase,
auth_challenges: final_auth_challenges,
}
let mut response_started_event : Json? = None
if mode == "provideResponse" && phase == "beforeRequestSent" {
response_started_event = self.build_synthetic_response_event_json(
request_id, input, response_overrides,
)
if response_started_event is None {
return
}
}
let response_completed_event = match
self.build_synthetic_response_event_json(
request_id, input, response_overrides,
) {
Some(event) => event
None => return
}
for cookie in response_cookies {
self.store_synthetic_response_cookie(context_id, cookie)
}
if mode == "provideResponse" {
let response_value = match response_body_value {
Some(response_value) => response_value
None => synthetic_navigation_synthesize_response_value(input.url)
}
self.remember_network_collected_data_internal(
context_id,
blocked_request_id,
input.request_value,
Some(response_value),
)
}
self.forget_network_blocked_request(blocked_request_id)
let is_navigation = match
get_map_field_with_alias(blocked_request, "isNavigation", "is_navigation") {
Some(True) => true
_ => false
}
let load_event = if input.navigation is Some(_) || is_navigation {
Some(
synthetic_network_load_event_json(context_id, input.navigation, input.url),
)
} else {
None
}
let applies_runtime_body_override = input.destination == "document" ||
input.destination == "script" ||
input.destination == "style"
if mode == "provideResponse" &&
applies_runtime_body_override &&
body_text is Some(_) {
self.apply_navigation_provide_response_body_override(
context_id,
input.destination,
body_text.unwrap(),
)
}
self.send_continue_blocked_response_result(
request_id,
response_started_event,
Some(response_completed_event),
None,
load_event,
true,
Some(status_code),
Some(reason_phrase),
body_text,
)
self.emit_synthetic_network_result_events(
response_started_event,
Some(response_completed_event),
None,
load_event,
)
if is_navigation {
self.complete_pending_navigation_response(
context_id,
Some(input.url),
input.navigation,
)
}
if mode == "provideResponse" {
self.maybe_emit_followup_preflight_blocked_request(blocked_request)
}
}
///|
fn BidiProtocol::apply_navigation_provide_response_body_override(
self : BidiProtocol,
context_id : String,
destination : String,
body_text : String,
) -> Unit {
ignore(self)
self.prepare_runtime_context_for_browsing_command(context_id)
match destination {
"script" =>
ignore(evaluate_js_with_console(body_text, false, false, false, "{}"))
"style" => {
let body_json = Json::string(body_text).stringify()
let expression = "(() => {" +
"if (!document.body && document.documentElement && typeof document.createElement === \"function\") {" +
"const body = document.createElement(\"body\");" +
"document.documentElement.appendChild(body);" +
"}" +
"if (document.body && !document.querySelector(\"div\") && typeof document.createElement === \"function\") {" +
"const div = document.createElement(\"div\");" +
"document.body.appendChild(div);" +
"}" +
"const style = document.createElement(\"style\");" +
"style.textContent = " +
body_json +
";" +
"const parent = document.head || document.documentElement;" +
"if (parent) parent.appendChild(style);" +
"const match = /color\\s*:\\s*([^;}\\n]+)/i.exec(style.textContent || \"\");" +
"const fallbackColor = match && match[1] ? String(match[1]).trim() : \"rgb(0, 0, 0)\";" +
"const originalGetComputedStyle = typeof globalThis.getComputedStyle === \"function\" ? globalThis.getComputedStyle.bind(globalThis) : null;" +
"const craterGetComputedStyle = function(el) {" +
"const base = originalGetComputedStyle ? originalGetComputedStyle(el) : ((el && el._style) || {});" +
"return new Proxy(base || {}, {" +
"get(target, prop) {" +
"if (prop === \"getPropertyValue\") {" +
"return function(name) {" +
"const raw = target && target[name] ? target[name] : \"\";" +
"if (String(name) === \"color\" && !raw) return fallbackColor;" +
"return raw || \"\";" +
"};" +
"}" +
"if (prop === \"color\") {" +
"const raw = target && target[prop] ? target[prop] : \"\";" +
"return raw || fallbackColor;" +
"}" +
"return target && target[prop] ? target[prop] : \"\";" +
"}" +
"});" +
"};" +
"globalThis.getComputedStyle = craterGetComputedStyle;" +
"window.getComputedStyle = craterGetComputedStyle;" +
"})()"
ignore(evaluate_js_with_console(expression, false, false, false, "{}"))
}
_ => {
let body_json = Json::string(body_text).stringify()
let expression = "(() => {" +
"if (!document.body && document.documentElement && typeof document.createElement === \"function\") {" +
"const body = document.createElement(\"body\");" +
"document.documentElement.appendChild(body);" +
"}" +
"if (document.body) {" +
"document.body.innerHTML = " +
body_json +
";" +
"}" +
"})()"
ignore(evaluate_js_with_console(expression, false, false, false, "{}"))
}
}
}
///|
fn BidiProtocol::send_continue_blocked_response_result(
self : BidiProtocol,
request_id : Int,
response_started_event : Json?,
response_completed_event : Json?,
auth_required_event : Json?,
load_event : Json?,
consumed : Bool,
status_code : Int?,
reason_phrase : String?,
body_text : String?,
) -> Unit {
let _ = self
let result : Map[String, Json] = {}
result["responseStartedEvent"] = match response_started_event {
Some(event) => event
None => Json::null()
}
result["responseCompletedEvent"] = match response_completed_event {
Some(event) => event
None => Json::null()
}
result["authRequiredEvent"] = match auth_required_event {
Some(event) => event
None => Json::null()
}
result["browsingContextLoad"] = match load_event {
Some(event) => event
None => Json::null()
}
result["consumed"] = Json::boolean(consumed)
match status_code {
Some(value) => result["statusCode"] = Json::number(value.to_double())
None => ()
}
match reason_phrase {
Some(value) => result["reasonPhrase"] = Json::string(value)
None => ()
}
match body_text {
Some(text) => result["bodyText"] = Json::string(text)
None => result["bodyText"] = Json::null()
}
self.send_success(request_id, Some(make_object(result)))
}
///|
fn BidiProtocol::emit_synthetic_network_event_if_subscribed(
self : BidiProtocol,
event_name : String,
event : Json?,
) -> Unit {
match event {
Some(Object(event_map)) =>
match event_map.get("context") {
Some(String(context_id)) =>
if self.is_subscribed_for_context(event_name, context_id) {
self.outbox.push(
Event({ event_method: event_name, params: make_object(event_map) }),
)
}
_ => ()
}
_ => ()
}
}
///|
fn BidiProtocol::emit_synthetic_network_result_events(
self : BidiProtocol,
response_started_event : Json?,
response_completed_event : Json?,
auth_required_event : Json?,
load_event : Json?,
) -> Unit {
self.emit_synthetic_network_event_if_subscribed(
"network.responseStarted", response_started_event,
)
self.emit_synthetic_network_event_if_subscribed(
"network.responseCompleted", response_completed_event,
)
self.emit_synthetic_network_event_if_subscribed(
"network.authRequired", auth_required_event,
)
match load_event {
Some(Object(load_map)) =>
self.outbox.push(
Event({
event_method: "browsingContext.load",
params: make_object(load_map),
}),
)
_ => ()
}
}
///|
fn BidiProtocol::maybe_emit_followup_preflight_blocked_request(
self : BidiProtocol,
blocked_request : Map[String, Json],
) -> Unit {
let http_method = match blocked_request.get("method") {
Some(String(name)) => name.to_upper()
_ => "GET"
}
let is_navigation = match
get_map_field_with_alias(blocked_request, "isNavigation", "is_navigation") {
Some(True) => true
_ => false
}
if http_method != "OPTIONS" || is_navigation {
return
}
let context_id = match
get_map_field_with_alias(blocked_request, "contextId", "context_id") {
Some(String(context_id)) => context_id
_ => return
}
let url = match blocked_request.get("url") {
Some(String(url)) => url
_ => return
}
let event_name = "network.beforeRequestSent"
if !self.is_subscribed_for_context(event_name, context_id) {
return
}
let intercepts = self.resolve_matching_network_intercepts_internal(
context_id, "beforeRequestSent", url,
)
if intercepts.length() == 0 {
return
}
let request_token = self.next_synthetic_network_request_id()
let request_headers = match blocked_request.get("request_headers") {
Some(Object(request_headers)) =>
synthetic_network_header_entries_from_object(request_headers)
_ => []
}
let resolved_request_headers = self.resolve_network_extra_header_entries(
context_id, request_headers,
)
let redirect_count = match blocked_request.get("redirect_count") {
Some(redirect_count) =>
storage_json_non_negative_int(redirect_count).unwrap_or(0)
None => 0
}
let navigation : String? = match blocked_request.get("navigation") {
Some(String(navigation)) => Some(navigation)
_ => None
}
let destination = match blocked_request.get("destination") {
Some(String(destination)) => destination
_ => ""
}
let initiator_type : String? = match
get_map_field_with_alias(blocked_request, "initiatorType", "initiator_type") {
Some(String(initiator_type)) => Some(initiator_type)
_ => Some("fetch")
}
let input : SyntheticNetworkEventInput = {
context_id,
url,
http_method: "GET",
request_token,
request_headers: resolved_request_headers,
request_value: None,
request_cookies: [],
request_cookies_override: false,
intercepts,
redirect_count,
navigation,
destination,
initiator_type,
}
let event = match
self.build_synthetic_before_request_sent_event_json(0, input) {
Some(event) => event
None => return
}
self.outbox.push(Event({ event_method: event_name, params: event }))
let followup_blocked_request : Map[String, Json] = {}
followup_blocked_request["phase"] = Json::string("beforeRequestSent")
followup_blocked_request["context_id"] = Json::string(context_id)
followup_blocked_request["url"] = Json::string(url)
followup_blocked_request["method"] = Json::string("GET")
followup_blocked_request["request_headers"] = match
blocked_request.get("request_headers") {
Some(Object(request_headers)) => make_object(request_headers)
_ => make_object({})
}
followup_blocked_request["redirect_count"] = Json::number(
redirect_count.to_double(),
)
followup_blocked_request["navigation"] = match navigation {
Some(navigation) => Json::string(navigation)
None => Json::null()
}
followup_blocked_request["destination"] = Json::string(destination)
followup_blocked_request["initiator_type"] = match initiator_type {
Some(initiator_type) => Json::string(initiator_type)
None => Json::null()
}
followup_blocked_request["is_navigation"] = Json::boolean(false)
self.remember_network_blocked_request(request_token, followup_blocked_request)
}
///|
fn BidiProtocol::normalize_synthetic_network_continue_credentials(
self : BidiProtocol,
request_id : Int,
raw_credentials : Json,
) -> SyntheticNetworkContinueCredentials? {
let map = match raw_credentials {
Object(map) => map
_ => {
self.send_error(
request_id, "invalid argument", "credentials must be an object",
)
return None
}
}
let credential_type = match map.get("type") {
Some(String(credential_type)) => credential_type
_ => {
self.send_error(
request_id, "invalid argument", "credentials.type must be a string",
)
return None
}
}
if credential_type != "password" {
self.send_error(
request_id, "invalid argument", "credentials.type is invalid",
)
return None
}
let username = match map.get("username") {
Some(String(username)) => username
_ => {
self.send_error(
request_id, "invalid argument", "credentials.username must be a string",
)
return None
}
}
let password = match map.get("password") {
Some(String(password)) => password
_ => {
self.send_error(
request_id, "invalid argument", "credentials.password must be a string",
)
return None
}
}
Some({ username, password })
}
///|
fn BidiProtocol::normalize_synthetic_network_bytes_value(
self : BidiProtocol,
request_id : Int,
raw_value : Json,
field_name : String,
) -> Json? {
let map = match raw_value {
Object(map) => map
_ => {
self.send_error(
request_id,
"invalid argument",
field_name + " must be an object",
)
return None
}
}
let value_type = match map.get("type") {
Some(String(value_type)) => value_type
_ => {
self.send_error(
request_id,
"invalid argument",
field_name + ".type must be a string",
)
return None
}
}
if value_type != "string" && value_type != "base64" {
self.send_error(
request_id,
"invalid argument",
field_name + ".type is invalid",
)
return None
}
let payload = match map.get("value") {
Some(String(payload)) => payload
_ => {
self.send_error(
request_id,
"invalid argument",
field_name + ".value must be a string",
)
return None
}
}
Some(
make_object({
"type": Json::string(value_type),
"value": Json::string(payload),
}),
)
}
///|
fn BidiProtocol::synthetic_network_text_from_bytes_value(
self : BidiProtocol,
request_id : Int,
raw_value : Json,
field_name : String,
) -> String? {
let map = match raw_value {
Object(map) => map
_ => {
self.send_error(
request_id,
"invalid argument",
field_name + " must be an object",
)
return None
}
}
let value_type = match map.get("type") {
Some(String(value_type)) => value_type
_ => {
self.send_error(
request_id,
"invalid argument",
field_name + ".type must be a string",
)
return None
}
}
let payload = match map.get("value") {
Some(String(payload)) => payload
_ => {
self.send_error(
request_id,
"invalid argument",
field_name + ".value must be a string",
)
return None
}
}
if value_type == "string" {
return Some(payload)
}
if value_type == "base64" {
return Some(decode_base64(payload))
}
self.send_error(
request_id,
"invalid argument",
field_name + ".type is invalid",
)
None
}
///|
fn BidiProtocol::synthetic_network_string_from_bytes_value(
self : BidiProtocol,
request_id : Int,
raw_value : Json,
field_name : String,
) -> String? {
let map = match raw_value {
Object(map) => map
_ => {
self.send_error(
request_id,
"invalid argument",
field_name + " must be an object",
)
return None
}
}
let value_type = match map.get("type") {
Some(String(value_type)) => value_type
_ => {
self.send_error(
request_id,
"invalid argument",
field_name + ".type must be a string",
)
return None
}
}
if value_type != "string" {
self.send_error(
request_id,
"invalid argument",
field_name + ".type is invalid",
)
return None
}
match map.get("value") {
Some(String(payload)) => Some(payload)
_ => {
self.send_error(
request_id,
"invalid argument",
field_name + ".value must be a string",
)
None
}
}
}
///|
fn BidiProtocol::normalize_synthetic_network_set_cookie_entries(
self : BidiProtocol,
request_id : Int,
raw_cookies : Json?,
base_url : String,
) -> SyntheticNetworkSetCookieEntries? {
let items = match raw_cookies {
Some(Array(items)) => items
_ => {
self.send_error(
request_id, "invalid argument", "cookies must be an array",
)
return None
}
}
let headers : Array[Json] = []
let cookies : Array[StoredCookie] = []
for item in items {
let map = match item {
Object(map) => map
_ => {
self.send_error(
request_id, "invalid argument", "cookie entry must be an object",
)
return None
}
}
let name = match map.get("name") {
Some(String(name)) =>
if is_valid_network_header_name_token(name) {
name
} else {
self.send_error(
request_id, "invalid argument", "cookie name is invalid",
)
return None
}
_ => {
self.send_error(
request_id, "invalid argument", "cookie name must be a string",
)
return None
}
}
let value = match map.get("value") {
Some(raw_value) =>
match
self.synthetic_network_string_from_bytes_value(
request_id, raw_value, "cookie.value",
) {
Some(value) => value
None => return None
}
None => {
self.send_error(
request_id, "invalid argument", "cookie value must be an object",
)
return None
}
}
let mut domain = storage_cookie_domain_from_base_url(base_url)
let mut domain_explicit = false
let mut path = "/"
let mut http_only = false
let mut secure = false
let mut same_site = "none"
let mut expiry : Int? = None
let header_entry : Map[String, Json] = {}
header_entry["name"] = Json::string(name)
header_entry["value"] = Json::string(value)
match map.get("domain") {
Some(String(raw_domain)) => {
let normalized_domain = storage_strip_leading_dot(
raw_domain.trim().to_string().to_lower(),
)
domain = normalized_domain
domain_explicit = normalized_domain != ""
header_entry["domain"] = Json::string(raw_domain)
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "cookie domain must be a string",
)
return None
}
None => ()
}
match map.get("expiry") {
Some(String(raw_expiry)) => {
header_entry["expiry"] = Json::string(raw_expiry)
let parsed_expiry = js_parse_cookie_expiry_seconds(raw_expiry)
if parsed_expiry >= 0 {
expiry = Some(parsed_expiry)
}
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "cookie expiry must be a string",
)
return None
}
None => ()
}
match map.get("path") {
Some(String(raw_path)) => {
path = raw_path
header_entry["path"] = Json::string(raw_path)
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "cookie path must be a string",
)
return None
}
None => ()
}
match map.get("sameSite") {
Some(String(raw_same_site)) => {
let lowered_same_site = raw_same_site.to_lower()
if lowered_same_site != "none" &&
lowered_same_site != "lax" &&
lowered_same_site != "strict" {
self.send_error(
request_id, "invalid argument", "cookie sameSite is invalid",
)
return None
}
same_site = lowered_same_site
header_entry["sameSite"] = Json::string(lowered_same_site)
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "cookie sameSite must be a string",
)
return None
}
None => ()
}
match map.get("httpOnly") {
Some(True) => {
http_only = true
header_entry["httpOnly"] = Json::boolean(true)
}
Some(False) => header_entry["httpOnly"] = Json::boolean(false)
Some(_) => {
self.send_error(
request_id, "invalid argument", "cookie httpOnly must be a boolean",
)
return None
}
None => ()
}
match map.get("secure") {
Some(True) => {
secure = true
header_entry["secure"] = Json::boolean(true)
}
Some(False) => header_entry["secure"] = Json::boolean(false)
Some(_) => {
self.send_error(
request_id, "invalid argument", "cookie secure must be a boolean",
)
return None
}
None => ()
}
match map.get("maxAge") {
Some(raw_max_age) =>
match
json_as_safe_int(raw_max_age, -9007199254740991.0, 9007199254740991.0) {
Some(max_age) => {
header_entry["maxAge"] = Json::number(max_age.to_double())
expiry = Some(storage_now_seconds() + max_age)
}
None => {
self.send_error(
request_id, "invalid argument", "cookie maxAge must be an integer",
)
return None
}
}
None => ()
}
cookies.push({
name,
value_text: value,
domain,
domain_explicit,
path,
http_only,
secure,
same_site,
expiry,
context_id: None,
user_context: None,
source_origin: None,
})
headers.push(
synthetic_network_header_string_entry(
"Set-Cookie",
synthetic_network_format_set_cookie_header(header_entry),
),
)
}
Some({ headers, cookies })
}
///|
fn synthetic_network_format_set_cookie_header(
entry : Map[String, Json],
) -> String {
let parts : Array[String] = []
let name = match entry.get("name") {
Some(String(name)) => name
_ => ""
}
let value = match entry.get("value") {
Some(String(value)) => value
_ => ""
}
parts.push(name + "=" + value)
match entry.get("path") {
Some(String(path)) => parts.push("Path=" + path)
_ => ()
}
match entry.get("domain") {
Some(String(domain)) => parts.push("Domain=" + domain)
_ => ()
}
match entry.get("expiry") {
Some(String(expiry)) => parts.push("Expires=" + expiry)
_ => ()
}
match entry.get("maxAge") {
Some(Number(max_age, ..)) =>
parts.push("Max-Age=" + max_age.to_int().to_string())
_ => ()
}
match entry.get("httpOnly") {
Some(True) => parts.push("HttpOnly")
_ => ()
}
match entry.get("secure") {
Some(True) => parts.push("Secure")
_ => ()
}
match entry.get("sameSite") {
Some(String(raw_same_site)) => {
let same_site = raw_same_site.to_lower()
let mapped = if same_site == "none" {
"None"
} else if same_site == "lax" {
"Lax"
} else if same_site == "strict" {
"Strict"
} else {
raw_same_site
}
parts.push("SameSite=" + mapped)
}
_ => ()
}
parts.join(";")
}
///|
fn synthetic_network_extract_set_cookie_entries(
headers : Array[Json],
base_url : String,
) -> Array[StoredCookie] {
let cookies : Array[StoredCookie] = []
for header in headers {
let map = match header {
Object(map) => map
_ => continue
}
let name = match map.get("name") {
Some(String(name)) => name
_ => continue
}
if !is_ascii_case_insensitive_equal(name, "set-cookie") {
continue
}
let value_map = match map.get("value") {
Some(Object(value_map)) => value_map
_ => continue
}
let value_type = match value_map.get("type") {
Some(String(value_type)) => value_type
_ => continue
}
if value_type != "string" {
continue
}
let header_value = match value_map.get("value") {
Some(String(header_value)) => header_value
_ => continue
}
match
synthetic_network_parse_set_cookie_header_value(header_value, base_url) {
Some(cookie) => cookies.push(cookie)
None => ()
}
}
cookies
}
///|
fn synthetic_network_parse_set_cookie_header_value(
header_value : String,
base_url : String,
) -> StoredCookie? {
let attributes : Array[String] = []
let mut first : String? = None
for raw_part in header_value.split(";") {
let part = raw_part.trim().to_string()
match first {
Some(_) => attributes.push(part)
None => first = Some(part)
}
}
let first = match first {
Some(first) => first
None => return None
}
match find_substring(first, "=", 0) {
Some(eq_idx) => {
let name = storage_slice(first, 0, eq_idx).trim().to_string()
let value_start = eq_idx + 1
let value = if value_start <= first.length() {
storage_slice_from(first, value_start).trim().to_string()
} else {
""
}
if name == "" || !is_valid_network_header_name_token(name) {
return None
}
let mut domain = storage_cookie_domain_from_base_url(base_url)
let mut domain_explicit = false
let mut path = "/"
let mut http_only = false
let mut secure = false
let mut same_site = "none"
let mut expiry : Int? = None
for raw_attr in attributes {
let attr = raw_attr.trim().to_string()
if attr == "" {
continue
}
let (attr_name, attr_value) = match find_substring(attr, "=", 0) {
Some(attr_eq_idx) => {
let attr_value_start = attr_eq_idx + 1
(
storage_slice(attr, 0, attr_eq_idx).trim().to_string().to_lower(),
if attr_value_start <= attr.length() {
storage_slice_from(attr, attr_value_start).trim().to_string()
} else {
""
},
)
}
None => (attr.to_lower(), "")
}
if attr_name == "domain" {
let normalized_domain = storage_strip_leading_dot(
attr_value.trim().to_string().to_lower(),
)
if normalized_domain != "" {
domain = normalized_domain
domain_explicit = true
}
} else if attr_name == "path" {
if attr_value != "" {
path = attr_value
}
} else if attr_name == "samesite" {
let lowered = attr_value.to_lower()
if lowered == "none" || lowered == "lax" || lowered == "strict" {
same_site = lowered
}
} else if attr_name == "max-age" {
let parsed_max_age : Int? = Some(@string.parse_int(attr_value)) catch {
_ => None
}
match parsed_max_age {
Some(parsed_max_age) =>
expiry = Some(storage_now_seconds() + parsed_max_age)
None => ()
}
} else if attr_name == "expires" {
let parsed_expiry = js_parse_cookie_expiry_seconds(attr_value)
if parsed_expiry >= 0 {
expiry = Some(parsed_expiry)
}
} else if attr_name == "httponly" {
http_only = true
} else if attr_name == "secure" {
secure = true
}
}
Some({
name,
value_text: value,
domain,
domain_explicit,
path,
http_only,
secure,
same_site,
expiry,
context_id: None,
user_context: None,
source_origin: None,
})
}
None => None
}
}
///|
fn synthetic_network_credentials_match_expected(
url : String,
credentials : SyntheticNetworkContinueCredentials?,
) -> Bool {
match credentials {
Some(credentials) =>
match synthetic_network_expected_auth_credentials(url) {
Some(expected) =>
credentials.username == expected.username &&
credentials.password == expected.password
None => true
}
None => false
}
}
///|
fn synthetic_network_expected_auth_credentials(
url : String,
) -> SyntheticNetworkContinueCredentials? {
let username = synthetic_network_first_query_value(url, "username")
let password = synthetic_network_first_query_value(url, "password")
match (username, password) {
(Some(username), Some(password)) => Some({ username, password })
_ => None
}
}
///|
fn BidiProtocol::build_synthetic_auth_required_event_json(
self : BidiProtocol,
request_id : Int,
input : SyntheticNetworkEventInput,
blocked_request : Map[String, Json],
) -> Json? {
let resolved_overrides = self.resolve_synthetic_response_overrides_internal(
input.context_id,
input.url,
input.http_method,
input.request_headers,
false,
)
let realm = synthetic_network_first_query_value(input.url, "realm").unwrap_or(
"testrealm",
)
let status = synthetic_network_blocked_response_status(blocked_request)
let response_overrides : SyntheticNetworkResponseOverrides = {
from_cache: resolved_overrides.from_cache,
headers: if synthetic_network_blocked_response_headers(blocked_request).length() >
0 {
synthetic_network_blocked_response_headers(blocked_request)
} else {
resolved_overrides.headers
},
mime_type: resolved_overrides.mime_type,
protocol: resolved_overrides.protocol,
status,
status_text: synthetic_network_blocked_response_status_text(
blocked_request, status,
),
auth_challenges: Some(
Json::array([
make_object({
"scheme": Json::string("Basic"),
"realm": Json::string(realm),
}),
]),
),
}
self.build_synthetic_response_event_json(
request_id, input, response_overrides,
)
}
///|
fn BidiProtocol::store_synthetic_response_cookie(
self : BidiProtocol,
context_id : String,
cookie : StoredCookie,
) -> Unit {
let stored_cookie : StoredCookie = {
..cookie,
context_id: Some(context_id),
user_context: Some(self.resolve_storage_user_context(context_id)),
source_origin: storage_cookie_origin_from_base_url(
self.get_context_cookie_base_url(context_id),
),
}
match stored_cookie.expiry {
Some(expiry) =>
if expiry <= storage_now_seconds() {
self.delete_matching_storage_cookie_identity(
stored_cookie.context_id,
stored_cookie.user_context,
stored_cookie.source_origin,
stored_cookie.name,
stored_cookie.domain,
stored_cookie.path,
)
} else {
self.store_storage_cookie(stored_cookie)
}
None => self.store_storage_cookie(stored_cookie)
}
}
///|
fn synthetic_network_load_event_json(
context_id : String,
navigation : String?,
url : String,
) -> Json {
make_object({
"context": Json::string(context_id),
"navigation": match navigation {
Some(navigation) => Json::string(navigation)
None => Json::null()
},
"timestamp": Json::number(input_now_ms()),
"url": Json::string(url),
})
}
///|
fn BidiProtocol::normalize_synthetic_network_event_input(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> SyntheticNetworkEventInput? {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return None
}
}
let context_id = match map.get("context") {
Some(String(context_id)) => context_id
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return None
}
}
if !self.manager.has_session(context_id) {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + context_id,
)
return None
}
let url = match map.get("url") {
Some(String(url)) => url
_ => {
self.send_error(request_id, "invalid argument", "url must be a string")
return None
}
}
let http_method = match map.get("method") {
Some(String(http_method)) => http_method
_ => {
self.send_error(request_id, "invalid argument", "method must be a string")
return None
}
}
let request_token = match
get_map_field_with_alias(map, "requestId", "request_id") {
Some(String(request_token)) => request_token
_ => {
self.send_error(
request_id, "invalid argument", "requestId must be a string",
)
return None
}
}
let request_headers = match
self.normalize_synthetic_network_header_entries(
request_id,
get_map_field_with_alias(map, "requestHeaders", "request_headers"),
) {
Some(request_headers) => request_headers
None => return None
}
let resolved_request_headers = self.resolve_network_extra_header_entries(
context_id, request_headers,
)
let request_cookies = match
self.normalize_synthetic_network_cookie_entries(
request_id,
get_map_field_with_alias(map, "requestCookies", "request_cookies"),
) {
Some(request_cookies) => request_cookies
None => return None
}
let intercepts = match
self.normalize_synthetic_network_string_list(
request_id,
get_param_raw(params, "intercepts"),
"intercepts",
) {
Some(intercepts) => intercepts
None => return None
}
let redirect_count = match get_param_raw(params, "redirectCount") {
None | Some(Null) => 0
Some(value) =>
match storage_json_non_negative_int(value) {
Some(count) => count
None => {
self.send_error(
request_id, "invalid argument", "redirectCount must be a non-negative integer",
)
return None
}
}
}
let navigation : String? = match get_param_raw(params, "navigation") {
None | Some(Null) => None
Some(String(value)) => Some(value)
Some(_) => {
self.send_error(
request_id, "invalid argument", "navigation must be a string",
)
return None
}
}
let destination = match get_param_raw(params, "destination") {
None | Some(Null) => ""
Some(String(value)) => value
Some(_) => {
self.send_error(
request_id, "invalid argument", "destination must be a string",
)
return None
}
}
let initiator_type : String? = match
get_map_field_with_alias(map, "initiatorType", "initiator_type") {
None | Some(Null) => None
Some(String(value)) => Some(value)
Some(_) => {
self.send_error(
request_id, "invalid argument", "initiatorType must be a string",
)
return None
}
}
Some({
context_id,
url,
http_method,
request_token,
request_headers: resolved_request_headers,
request_value: get_map_field_with_alias(
map, "requestValue", "request_value",
),
request_cookies: request_cookies.cookies,
request_cookies_override: request_cookies.enabled,
intercepts,
redirect_count,
navigation,
destination,
initiator_type,
})
}
///|
fn BidiProtocol::synthetic_network_event_input_from_blocked_request(
self : BidiProtocol,
request_id : Int,
blocked_request_id : String,
blocked_request : Map[String, Json],
) -> SyntheticNetworkEventInput? {
let context_id = match
get_map_field_with_alias(blocked_request, "contextId", "context_id") {
Some(String(context_id)) => context_id
_ => {
self.send_error(request_id, "no such request", "Unknown request")
return None
}
}
if !self.manager.has_session(context_id) {
self.send_error(request_id, "no such request", "Unknown request")
return None
}
let url = match blocked_request.get("url") {
Some(String(url)) => url
_ => ""
}
let http_method = match blocked_request.get("method") {
Some(String(http_method)) => http_method
_ => "GET"
}
let request_headers = match blocked_request.get("request_headers") {
Some(Object(request_headers)) =>
synthetic_network_header_entries_from_object(request_headers)
_ => []
}
let resolved_request_headers = self.resolve_network_extra_header_entries(
context_id, request_headers,
)
let redirect_count = match blocked_request.get("redirect_count") {
Some(redirect_count) =>
storage_json_non_negative_int(redirect_count).unwrap_or(0)
None => 0
}
let navigation : String? = match blocked_request.get("navigation") {
Some(String(navigation)) => Some(navigation)
_ => None
}
let destination = match blocked_request.get("destination") {
Some(String(destination)) => destination
_ => ""
}
let initiator_type : String? = match
get_map_field_with_alias(blocked_request, "initiatorType", "initiator_type") {
Some(String(initiator_type)) => Some(initiator_type)
_ => Some("fetch")
}
Some({
context_id,
url,
http_method,
request_token: blocked_request_id,
request_headers: resolved_request_headers,
request_value: get_map_field_with_alias(
blocked_request, "requestValue", "request_value",
),
request_cookies: [],
request_cookies_override: false,
intercepts: [],
redirect_count,
navigation,
destination,
initiator_type,
})
}
///|
fn synthetic_network_blocked_response_headers(
blocked_request : Map[String, Json],
) -> Array[Json] {
let headers : Array[Json] = []
match
get_map_field_with_alias(
blocked_request, "responseHeaders", "response_headers",
) {
Some(Array(items)) =>
for item in items {
match validate_network_extra_header_entry(item) {
Some(header) => headers.push(header)
None => ()
}
}
_ => ()
}
headers
}
///|
fn synthetic_network_blocked_response_status(
blocked_request : Map[String, Json],
) -> Int {
match
get_map_field_with_alias(
blocked_request, "responseStatus", "response_status",
) {
Some(status_value) =>
match storage_json_non_negative_int(status_value) {
Some(status) => status
None => 401
}
None => 401
}
}
///|
fn synthetic_network_blocked_response_status_text(
blocked_request : Map[String, Json],
status : Int,
) -> String {
match
get_map_field_with_alias(
blocked_request, "responseStatusText", "response_status_text",
) {
Some(String(status_text)) =>
if status_text == "" {
synthetic_network_status_text_for_code(status)
} else {
status_text
}
_ => synthetic_network_status_text_for_code(status)
}
}
///|
fn BidiProtocol::normalize_synthetic_network_header_entries(
self : BidiProtocol,
request_id : Int,
raw_headers : Json?,
) -> Array[Json]? {
match raw_headers {
None | Some(Null) => Some([])
Some(Array(items)) => {
let headers : Array[Json] = []
for item in items {
match validate_network_extra_header_entry(item) {
Some(header) => headers.push(header)
None => {
self.send_error(
request_id, "invalid argument", "requestHeaders entry is invalid",
)
return None
}
}
}
Some(headers)
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "requestHeaders must be an array",
)
None
}
}
}
///|
fn BidiProtocol::apply_network_extra_headers_options(
self : BidiProtocol,
options : Map[String, Json],
) -> Unit {
let normalized = match options.get("headers") {
Some(Array(headers)) =>
synthetic_network_header_string_map_from_entries(headers)
_ => {}
}
match options.get("contexts") {
Some(Array(items)) => {
for item in items {
match item {
String(ctx_id) =>
if normalized.length() == 0 {
self.network_extra_headers_by_context.remove(ctx_id)
} else {
self.network_extra_headers_by_context[ctx_id] = synthetic_network_copy_string_map(
normalized,
)
}
_ => ()
}
}
return
}
_ => ()
}
match options.get("userContexts") {
Some(Array(items)) => {
for item in items {
match item {
String(user_context_id) =>
if normalized.length() == 0 {
self.network_extra_headers_by_user_context.remove(user_context_id)
} else {
self.network_extra_headers_by_user_context[user_context_id] = synthetic_network_copy_string_map(
normalized,
)
}
_ => ()
}
}
return
}
_ => ()
}
synthetic_network_replace_string_map(
self.network_extra_headers_global,
normalized,
)
}
///|
fn BidiProtocol::resolve_network_extra_header_entries(
self : BidiProtocol,
context_id : String,
request_headers : Array[Json],
) -> Array[Json] {
let merged : Map[String, String] = {}
synthetic_network_merge_string_map(merged, self.network_extra_headers_global)
let user_context_id = self.resolve_storage_user_context(context_id)
match self.network_extra_headers_by_user_context.get(user_context_id) {
Some(headers) => synthetic_network_merge_string_map(merged, headers)
None => ()
}
for scope_id in self.storage_context_ancestry(context_id) {
match self.network_extra_headers_by_context.get(scope_id) {
Some(headers) => synthetic_network_merge_string_map(merged, headers)
None => ()
}
}
synthetic_network_merge_string_map(
merged,
synthetic_network_header_string_map_from_entries(request_headers),
)
synthetic_network_header_entries_from_string_map(merged)
}
///|
fn BidiProtocol::build_network_headers_echo_payload(
self : BidiProtocol,
context_id : String,
request_headers : Array[Json],
) -> String {
let headers : Map[String, Json] = {}
let mut has_user_agent = false
let mut has_accept_language = false
for
header in self.resolve_network_extra_header_entries(
context_id, request_headers,
) {
let name = match synthetic_network_header_entry_name(header) {
Some(name) => name
None => continue
}
let value = match synthetic_network_header_entry_string_value(header) {
Some(value) => value
None => continue
}
if name == "user-agent" {
has_user_agent = true
}
if name == "accept-language" {
has_accept_language = true
}
headers[name] = Json::array([Json::string(value)])
}
if !has_user_agent {
headers["user-agent"] = Json::array([
Json::string(self.resolve_effective_user_agent(context_id)),
])
}
if !has_accept_language {
headers["accept-language"] = Json::array([
Json::string(self.resolve_effective_locale(context_id)),
])
}
make_object({ "headers": make_object(headers) }).stringify()
}
///|
fn synthetic_network_copy_string_map(
source : Map[String, String],
) -> Map[String, String] {
let copied : Map[String, String] = {}
synthetic_network_merge_string_map(copied, source)
copied
}
///|
fn synthetic_network_replace_string_map(
target : Map[String, String],
source : Map[String, String],
) -> Unit {
let keys : Array[String] = []
for key, _ in target {
keys.push(key)
}
for key in keys {
target.remove(key)
}
synthetic_network_merge_string_map(target, source)
}
///|
fn synthetic_network_merge_string_map(
target : Map[String, String],
source : Map[String, String],
) -> Unit {
for key, value in source {
target[key] = value
}
}
///|
fn synthetic_network_header_string_map_from_entries(
headers : Array[Json],
) -> Map[String, String] {
let normalized : Map[String, String] = {}
for header in headers {
let name = match synthetic_network_header_entry_name(header) {
Some(name) => name.to_lower()
None => continue
}
let value = match synthetic_network_header_entry_string_value(header) {
Some(value) => value
None => continue
}
normalized[name] = value
}
normalized
}
///|
fn synthetic_network_header_entries_from_string_map(
headers : Map[String, String],
) -> Array[Json] {
let entries : Array[Json] = []
for name, value in headers {
entries.push(synthetic_network_header_string_entry(name, value))
}
entries
}
///|
fn BidiProtocol::normalize_synthetic_network_cookie_entries(
self : BidiProtocol,
request_id : Int,
raw_cookies : Json?,
) -> SyntheticNetworkCookieOverride? {
match raw_cookies {
None => Some({ enabled: false, cookies: [] })
Some(Null) => Some({ enabled: true, cookies: [] })
Some(Array(items)) => {
let cookies : Array[Json] = []
for item in items {
let map = match item {
Object(map) => map
_ => {
self.send_error(
request_id, "invalid argument", "requestCookies entry must be an object",
)
return None
}
}
let name = match map.get("name") {
Some(String(name)) => name
_ => {
self.send_error(
request_id, "invalid argument", "requestCookies.name must be a string",
)
return None
}
}
let value = match map.get("value") {
Some(Object(value_map)) => {
let value_type = match value_map.get("type") {
Some(String(value_type)) => value_type
_ => {
self.send_error(
request_id, "invalid argument", "requestCookies.value.type must be a string",
)
return None
}
}
let payload = match value_map.get("value") {
Some(String(payload)) => payload
_ => {
self.send_error(
request_id, "invalid argument", "requestCookies.value.value must be a string",
)
return None
}
}
if value_type != "string" {
self.send_error(
request_id, "invalid argument", "requestCookies.value.type is invalid",
)
return None
}
make_object({
"type": Json::string("string"),
"value": Json::string(payload),
})
}
_ => {
self.send_error(
request_id, "invalid argument", "requestCookies.value must be an object",
)
return None
}
}
cookies.push(
make_object({ "name": Json::string(name), "value": value }),
)
}
Some({ enabled: true, cookies })
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "requestCookies must be an array",
)
None
}
}
}
///|
fn BidiProtocol::normalize_synthetic_network_continue_url(
self : BidiProtocol,
request_id : Int,
raw_url : Json,
) -> String? {
let url = match raw_url {
String(url) => url
_ => {
self.send_error(request_id, "invalid argument", "url must be a string")
return None
}
}
if synthetic_network_is_valid_continue_url(url) {
Some(url)
} else {
self.send_error(request_id, "invalid argument", "url is invalid")
None
}
}
///|
fn BidiProtocol::normalize_synthetic_network_string_list(
self : BidiProtocol,
request_id : Int,
raw_list : Json?,
field_name : String,
) -> Array[String]? {
match raw_list {
None | Some(Null) => Some([])
Some(Array(items)) => {
let values : Array[String] = []
for item in items {
match item {
String(value) => values.push(value)
_ => {
self.send_error(
request_id,
"invalid argument",
field_name + " entries must be strings",
)
return None
}
}
}
Some(values)
}
Some(_) => {
self.send_error(
request_id,
"invalid argument",
field_name + " must be an array",
)
None
}
}
}
///|
fn BidiProtocol::normalize_synthetic_network_response_overrides(
self : BidiProtocol,
request_id : Int,
raw_overrides : Json?,
) -> SyntheticNetworkResponseOverrides? {
let overrides = match raw_overrides {
None | Some(Null) =>
return Some({
from_cache: false,
headers: [],
mime_type: "text/plain",
protocol: "http/1.1",
status: 200,
status_text: "OK",
auth_challenges: None,
})
Some(Object(map)) => map
Some(_) => {
self.send_error(
request_id, "invalid argument", "responseOverrides must be an object",
)
return None
}
}
let from_cache = match overrides.get("fromCache") {
Some(True) => true
Some(False) | None => false
Some(_) => {
self.send_error(
request_id, "invalid argument", "responseOverrides.fromCache must be a boolean",
)
return None
}
}
let headers = match
self.normalize_synthetic_network_header_entries(
request_id,
overrides.get("headers"),
) {
Some(headers) => headers
None => return None
}
let mime_type = match overrides.get("mimeType") {
Some(String(value)) => value
None => "text/plain"
Some(_) => {
self.send_error(
request_id, "invalid argument", "responseOverrides.mimeType must be a string",
)
return None
}
}
let protocol = match overrides.get("protocol") {
Some(String(value)) => value
None => "http/1.1"
Some(_) => {
self.send_error(
request_id, "invalid argument", "responseOverrides.protocol must be a string",
)
return None
}
}
let status = match overrides.get("status") {
Some(value) =>
match storage_json_non_negative_int(value) {
Some(status) => status
None => {
self.send_error(
request_id, "invalid argument", "responseOverrides.status must be a non-negative integer",
)
return None
}
}
None => 200
}
let status_text = match overrides.get("statusText") {
Some(String(value)) => value
None => synthetic_network_status_text_for_code(status)
Some(_) => {
self.send_error(
request_id, "invalid argument", "responseOverrides.statusText must be a string",
)
return None
}
}
let mut auth_challenges : Json? = match overrides.get("authChallenges") {
None | Some(Null) => None
Some(Array(values)) => Some(Json::array(values))
Some(_) => {
self.send_error(
request_id, "invalid argument", "responseOverrides.authChallenges must be an array",
)
return None
}
}
let missing_auth_challenges = match auth_challenges {
None => true
Some(_) => false
}
if (status == 401 || status == 407) && missing_auth_challenges {
auth_challenges = Some(Json::array([]))
}
Some({
from_cache,
headers,
mime_type,
protocol,
status,
status_text,
auth_challenges,
})
}
///|
fn BidiProtocol::build_synthetic_before_request_sent_event_json(
self : BidiProtocol,
request_id : Int,
input : SyntheticNetworkEventInput,
) -> Json? {
let now = input_now_ms()
let request_payload = match
self.build_synthetic_network_request_payload_json(request_id, input, now) {
Some(request_payload) => request_payload
None => return None
}
Some(
make_object(
self.build_synthetic_network_event_base_map(input, request_payload, now),
),
)
}
///|
fn BidiProtocol::build_synthetic_response_event_json(
self : BidiProtocol,
request_id : Int,
input : SyntheticNetworkEventInput,
response_overrides : SyntheticNetworkResponseOverrides,
) -> Json? {
let now = input_now_ms()
let request_payload = match
self.build_synthetic_network_request_payload_json(request_id, input, now) {
Some(request_payload) => request_payload
None => return None
}
let event = self.build_synthetic_network_event_base_map(
input, request_payload, now,
)
event["response"] = self.build_synthetic_network_response_payload_json(
input.url,
response_overrides,
)
Some(make_object(event))
}
///|
fn BidiProtocol::build_synthetic_fetch_error_event_json(
self : BidiProtocol,
request_id : Int,
input : SyntheticNetworkEventInput,
error_text : String,
) -> Json? {
let now = input_now_ms()
let request_payload = match
self.build_synthetic_network_request_payload_json(request_id, input, now) {
Some(request_payload) => request_payload
None => return None
}
let event = self.build_synthetic_network_event_base_map(
input, request_payload, now,
)
event["errorText"] = Json::string(error_text)
Some(make_object(event))
}
///|
fn BidiProtocol::build_synthetic_network_request_payload_json(
self : BidiProtocol,
request_id : Int,
input : SyntheticNetworkEventInput,
now : Double,
) -> Json? {
let body_size = match
self.synthetic_network_request_body_size(request_id, input.request_value) {
Some(body_size) => body_size
None => return None
}
let cookies = if input.request_cookies_override {
input.request_cookies
} else {
self.resolve_storage_request_cookies(input.context_id, Some(input.url))
}
let request : Map[String, Json] = {}
request["method"] = Json::string(input.http_method)
request["bodySize"] = Json::number(body_size.to_double())
request["cookies"] = Json::array(cookies)
request["destination"] = Json::string(input.destination)
request["headers"] = Json::array(input.request_headers)
request["headersSize"] = Json::number(0.0)
request["initiatorType"] = match input.initiator_type {
Some(value) => Json::string(value)
None => Json::null()
}
request["request"] = Json::string(input.request_token)
request["timings"] = self.build_synthetic_network_timings_json(now)
request["url"] = Json::string(input.url)
Some(make_object(request))
}
///|
fn BidiProtocol::build_synthetic_network_event_base_map(
self : BidiProtocol,
input : SyntheticNetworkEventInput,
request_payload : Json,
now : Double,
) -> Map[String, Json] {
let _ = self
let event : Map[String, Json] = {}
event["context"] = Json::string(input.context_id)
event["isBlocked"] = Json::boolean(input.intercepts.length() > 0)
event["navigation"] = match input.navigation {
Some(value) => Json::string(value)
None => Json::null()
}
event["redirectCount"] = Json::number(input.redirect_count.to_double())
event["request"] = request_payload
event["timestamp"] = Json::number(now)
if input.intercepts.length() > 0 {
let intercepts : Array[Json] = []
for intercept in input.intercepts {
intercepts.push(Json::string(intercept))
}
event["intercepts"] = Json::array(intercepts)
}
event
}
///|
fn BidiProtocol::build_synthetic_network_timings_json(
self : BidiProtocol,
now : Double,
) -> Json {
let _ = self
make_object({
"timeOrigin": Json::number(now),
"requestTime": Json::number(0.0),
"redirectStart": Json::number(0.0),
"redirectEnd": Json::number(0.0),
"fetchStart": Json::number(0.0),
"dnsStart": Json::number(0.0),
"dnsEnd": Json::number(0.0),
"connectStart": Json::number(0.0),
"connectEnd": Json::number(0.0),
"tlsStart": Json::number(0.0),
"requestStart": Json::number(0.0),
"responseStart": Json::number(0.0),
"responseEnd": Json::number(0.0),
})
}
///|
fn BidiProtocol::build_synthetic_network_response_payload_json(
self : BidiProtocol,
url : String,
response_overrides : SyntheticNetworkResponseOverrides,
) -> Json {
let _ = self
let response : Map[String, Json] = {}
response["bodySize"] = Json::number(0.0)
response["bytesReceived"] = Json::number(0.0)
response["content"] = make_object({ "size": Json::number(0.0) })
response["fromCache"] = Json::boolean(response_overrides.from_cache)
response["headers"] = Json::array(response_overrides.headers)
response["headersSize"] = Json::number(0.0)
response["mimeType"] = Json::string(response_overrides.mime_type)
response["protocol"] = Json::string(response_overrides.protocol)
response["status"] = Json::number(response_overrides.status.to_double())
response["statusText"] = Json::string(response_overrides.status_text)
response["url"] = Json::string(url)
match response_overrides.auth_challenges {
Some(auth_challenges) => response["authChallenges"] = auth_challenges
None => ()
}
make_object(response)
}
///|
fn BidiProtocol::synthetic_network_request_body_size(
self : BidiProtocol,
request_id : Int,
raw_value : Json?,
) -> Int? {
let _ = self
match raw_value {
None | Some(Null) => Some(0)
Some(Object(map)) => {
let value_type = match map.get("type") {
Some(String(value_type)) => value_type
_ => {
self.send_error(
request_id, "invalid argument", "requestValue.type must be a string",
)
return None
}
}
let payload = match map.get("value") {
Some(String(payload)) => payload
_ => {
self.send_error(
request_id, "invalid argument", "requestValue.value must be a string",
)
return None
}
}
if value_type == "string" {
return Some(js_utf8_byte_length(payload))
}
if value_type == "base64" {
return Some(js_base64_byte_length(payload))
}
self.send_error(
request_id, "invalid argument", "requestValue.type is invalid",
)
None
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "requestValue must be an object",
)
None
}
}
}
///|
fn BidiProtocol::handle_network_set_cache_behavior(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let cache_behavior = match
get_map_field_with_alias(map, "cacheBehavior", "cache_behavior") {
Some(String(cache_behavior)) => cache_behavior
Some(_) => {
self.send_error(
request_id, "invalid argument", "cacheBehavior must be a string",
)
return
}
None => {
self.send_error(request_id, "invalid argument", "Missing cacheBehavior")
return
}
}
if cache_behavior != "default" && cache_behavior != "bypass" {
self.send_error(request_id, "invalid argument", "cacheBehavior is invalid")
return
}
match map.get("contexts") {
None | Some(Null) => {
self.network_cache_behavior_global = cache_behavior
let keys : Array[String] = []
for key, _ in self.network_cache_behavior_by_context {
keys.push(key)
}
for key in keys {
self.network_cache_behavior_by_context.remove(key)
}
}
Some(Array(items)) => {
if items.length() == 0 {
self.send_error(
request_id, "invalid argument", "contexts must not be empty",
)
return
}
for item in items {
match item {
String(ctx_id) =>
if !self.manager.has_session(ctx_id) {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
return
} else {
self.network_cache_behavior_by_context[ctx_id] = cache_behavior
}
_ => {
self.send_error(
request_id, "invalid argument", "contexts entries must be strings",
)
return
}
}
}
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "contexts must be an array",
)
return
}
}
self.send_success(request_id, Some(make_object({})))
}
///|
fn BidiProtocol::handle_network_resolve_synthetic_response_overrides(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let context_id = match map.get("context") {
Some(String(context_id)) => context_id
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return
}
}
if !self.manager.has_session(context_id) {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + context_id,
)
return
}
let url = match map.get("url") {
Some(String(url)) => url
_ => {
self.send_error(request_id, "invalid argument", "url must be a string")
return
}
}
let http_method = match map.get("method") {
Some(String(http_method)) => http_method
_ => {
self.send_error(request_id, "invalid argument", "method must be a string")
return
}
}
let request_headers = match
self.normalize_synthetic_network_header_entries(
request_id,
get_map_field_with_alias(map, "requestHeaders", "request_headers"),
) {
Some(request_headers) => request_headers
None => return
}
let resolved_request_headers = self.resolve_network_extra_header_entries(
context_id, request_headers,
)
let update_cache = match
get_map_field_with_alias(map, "updateCache", "update_cache") {
None | Some(Null) => true
Some(True) => true
Some(False) => false
Some(_) => {
self.send_error(
request_id, "invalid argument", "updateCache must be a boolean",
)
return
}
}
let overrides = self.resolve_synthetic_response_overrides_internal(
context_id, url, http_method, resolved_request_headers, update_cache,
)
self.send_success(
request_id,
Some(
make_object({
"responseOverrides": self.synthetic_network_response_overrides_to_json(
overrides,
),
}),
),
)
}
///|
fn BidiProtocol::handle_network_remember_blocked_request(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let blocked_request_id = match map.get("request") {
Some(String(blocked_request_id)) => blocked_request_id
_ => {
self.send_error(
request_id, "invalid argument", "request must be a string",
)
return
}
}
let blocked_request = match
get_map_field_with_alias(map, "blockedRequest", "blocked_request") {
Some(Object(blocked_request)) => blocked_request
_ => {
self.send_error(
request_id, "invalid argument", "blockedRequest must be an object",
)
return
}
}
self.remember_network_blocked_request(blocked_request_id, blocked_request)
self.send_success(request_id, Some(make_object({})))
}
///|
fn BidiProtocol::handle_network_get_blocked_request(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let blocked_request_id = match
self.synthetic_network_request_lookup_id(request_id, params) {
Some(blocked_request_id) => blocked_request_id
None => return
}
let blocked_request = match
self.get_network_blocked_request(blocked_request_id) {
Some(blocked_request) => make_object(blocked_request)
None => Json::null()
}
self.send_success(
request_id,
Some(make_object({ "blockedRequest": blocked_request })),
)
}
///|
fn BidiProtocol::handle_network_get_blocked_request_phase_value(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
match
self.resolve_network_blocked_request_field(request_id, params, "phase") {
Some(value) => self.send_success(request_id, Some(value))
None => ()
}
}
///|
fn BidiProtocol::handle_network_get_blocked_request_navigation_value(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
match
self.resolve_network_blocked_request_field(request_id, params, "navigation") {
Some(value) => self.send_success(request_id, Some(value))
None => ()
}
}
///|
fn BidiProtocol::resolve_network_blocked_request_field(
self : BidiProtocol,
request_id : Int,
params : Json?,
field_name : String,
) -> Json? {
let blocked_request_id = match
self.synthetic_network_request_lookup_id(request_id, params) {
Some(blocked_request_id) => blocked_request_id
None => return None
}
let blocked_request = match
self.get_network_blocked_request(blocked_request_id) {
Some(blocked_request) => blocked_request
None => return Some(Json::null())
}
match blocked_request.get(field_name) {
Some(value) => Some(value)
None => Some(Json::null())
}
}
///|
fn BidiProtocol::handle_network_forget_blocked_request(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let blocked_request_id = match
self.synthetic_network_request_lookup_id(request_id, params) {
Some(blocked_request_id) => blocked_request_id
None => return
}
self.forget_network_blocked_request(blocked_request_id)
self.send_success(request_id, Some(make_object({})))
}
///|
fn BidiProtocol::handle_network_has_blocked_navigation_request(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Unit {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return
}
}
let context_id = match map.get("context") {
Some(String(context_id)) => context_id
_ => {
self.send_error(
request_id, "invalid argument", "context must be a string",
)
return
}
}
let navigation_id : String? = match map.get("navigation") {
None | Some(Null) => None
Some(String(navigation_id)) => Some(navigation_id)
Some(_) => {
self.send_error(
request_id, "invalid argument", "navigation must be a string",
)
return
}
}
self.send_success(
request_id,
Some(
make_object({
"blocked": Json::boolean(
self.has_network_blocked_navigation_request(context_id, navigation_id),
),
}),
),
)
}
///|
fn BidiProtocol::clear_all_network_state(self : BidiProtocol) -> Unit {
self.network_cache_behavior_global = "default"
synthetic_network_replace_string_map(self.network_extra_headers_global, {})
let extra_header_context_keys : Array[String] = []
for key, _ in self.network_extra_headers_by_context {
extra_header_context_keys.push(key)
}
for key in extra_header_context_keys {
self.network_extra_headers_by_context.remove(key)
}
let extra_header_user_context_keys : Array[String] = []
for key, _ in self.network_extra_headers_by_user_context {
extra_header_user_context_keys.push(key)
}
for key in extra_header_user_context_keys {
self.network_extra_headers_by_user_context.remove(key)
}
let behavior_keys : Array[String] = []
for key, _ in self.network_cache_behavior_by_context {
behavior_keys.push(key)
}
for key in behavior_keys {
self.network_cache_behavior_by_context.remove(key)
}
let cache_keys : Array[String] = []
for key, _ in self.network_cached_requests_by_context {
cache_keys.push(key)
}
for key in cache_keys {
self.network_cached_requests_by_context.remove(key)
}
let blocked_keys : Array[String] = []
for key, _ in self.network_blocked_requests {
blocked_keys.push(key)
}
for key in blocked_keys {
self.network_blocked_requests.remove(key)
}
}
///|
fn BidiProtocol::clear_network_state_for_context(
self : BidiProtocol,
ctx_id : String,
) -> Unit {
self.network_extra_headers_by_context.remove(ctx_id)
self.network_cache_behavior_by_context.remove(ctx_id)
self.network_state_channel_by_context.remove(ctx_id)
self.network_cached_requests_by_context.remove(ctx_id)
let blocked_keys : Array[String] = []
for request_id, blocked_request in self.network_blocked_requests {
match blocked_request {
Object(map) =>
match map.get("context_id") {
Some(String(blocked_ctx_id)) =>
if blocked_ctx_id == ctx_id {
blocked_keys.push(request_id)
}
_ => ()
}
_ => ()
}
}
for request_id in blocked_keys {
self.network_blocked_requests.remove(request_id)
}
}
///|
fn BidiProtocol::remember_network_blocked_request(
self : BidiProtocol,
request_id : String,
payload : Map[String, Json],
) -> Unit {
let normalized : Map[String, Json] = {}
for key, value in payload {
normalized[key] = value
}
normalized["request"] = Json::string(request_id)
self.network_blocked_requests[request_id] = make_object(normalized)
}
///|
fn BidiProtocol::get_network_blocked_request(
self : BidiProtocol,
request_id : String,
) -> Map[String, Json]? {
match self.network_blocked_requests.get(request_id) {
Some(Object(blocked_request)) => Some(blocked_request)
_ => None
}
}
///|
fn BidiProtocol::forget_network_blocked_request(
self : BidiProtocol,
request_id : String,
) -> Unit {
self.network_blocked_requests.remove(request_id)
}
///|
fn BidiProtocol::has_network_blocked_navigation_request(
self : BidiProtocol,
ctx_id : String,
navigation_id : String?,
) -> Bool {
for _, blocked_request in self.network_blocked_requests {
match blocked_request {
Object(map) => {
let blocked_ctx_id = match map.get("context_id") {
Some(String(blocked_ctx_id)) => blocked_ctx_id
_ => continue
}
if blocked_ctx_id != ctx_id {
continue
}
match map.get("is_navigation") {
Some(True) => ()
_ => continue
}
match navigation_id {
Some(navigation_id) =>
match map.get("navigation") {
Some(String(blocked_navigation_id)) =>
if blocked_navigation_id == navigation_id {
return true
}
_ => ()
}
None => return true
}
}
_ => ()
}
}
false
}
///|
fn BidiProtocol::resolve_synthetic_response_overrides_internal(
self : BidiProtocol,
context_id : String,
url : String,
http_method : String,
request_headers : Array[Json],
update_cache : Bool,
) -> SyntheticNetworkResponseOverrides {
let mut status = 200
let mut from_cache = false
let mut protocol = "http/1.1"
let mut auth_challenges : Json? = None
let headers : Array[Json] = []
let path = synthetic_network_request_path(url)
let method_upper = http_method.to_upper()
let cache_enabled = self.synthetic_network_cache_enabled(context_id)
let cache_key = synthetic_network_cache_key(method_upper, url)
let context_cache = self.network_cached_requests_by_context
.get(context_id)
.unwrap_or({})
if url.has_prefix("data:") {
protocol = "data"
}
if path.has_suffix("/redirect.py") {
status = synthetic_network_int_query_param(url, "status", 302)
} else if path.has_suffix("/status.py") {
status = synthetic_network_int_query_param(url, "status", 200)
} else if path.has_suffix("/cached.py") {
status = synthetic_network_int_query_param(url, "status", 200)
if cache_enabled && method_upper == "GET" {
if context_cache.contains(cache_key) {
from_cache = true
} else if update_cache {
context_cache[cache_key] = true
self.network_cached_requests_by_context[context_id] = context_cache
}
}
} else if path.has_suffix("/must-revalidate.py") {
let return_304 = synthetic_network_request_header_string_value(
request_headers, "return-304",
)
if cache_enabled &&
(
method_upper == "GET" ||
method_upper == "HEAD" ||
method_upper == "OPTIONS"
) &&
return_304 == Some("true") &&
context_cache.contains(cache_key) {
status = 304
} else {
status = 200
if cache_enabled &&
(
method_upper == "GET" ||
method_upper == "HEAD" ||
method_upper == "OPTIONS"
) &&
update_cache {
context_cache[cache_key] = true
self.network_cached_requests_by_context[context_id] = context_cache
}
}
} else if path.has_suffix("/authentication.py") {
status = 401
let realm = synthetic_network_first_query_value(url, "realm").unwrap_or(
"testrealm",
)
auth_challenges = Some(
Json::array([
make_object({
"scheme": Json::string("Basic"),
"realm": Json::string(realm),
}),
]),
)
headers.push(
synthetic_network_header_string_entry(
"WWW-Authenticate",
"Basic realm=\"" + realm + "\"",
),
)
}
if path.has_suffix("/headers.py") {
for raw_header in synthetic_network_query_values(url, "header") {
match find_substring(raw_header, ":", 0) {
Some(idx) => {
let name = raw_header.unsafe_substring(start=0, end=idx)
let value_start = idx + 1
let value = if value_start <= raw_header.length() {
raw_header.unsafe_substring(
start=value_start,
end=raw_header.length(),
)
} else {
""
}
headers.push(synthetic_network_header_string_entry(name, value))
}
None => ()
}
}
}
let mime_type = synthetic_network_infer_mime_type(url, path)
if mime_type != "" {
headers.push(
synthetic_network_header_string_entry("Content-Type", mime_type),
)
}
let mut status_text = synthetic_network_status_text_for_code(status)
if path.has_suffix("/serviceworker.html") {
status_text = "OK from serviceworker"
}
let missing_auth_challenges = match auth_challenges {
None => true
Some(_) => false
}
if (status == 401 || status == 407) && missing_auth_challenges {
auth_challenges = Some(Json::array([]))
}
{
from_cache,
headers,
mime_type,
protocol,
status,
status_text,
auth_challenges,
}
}
///|
fn BidiProtocol::synthetic_network_response_overrides_to_json(
self : BidiProtocol,
overrides : SyntheticNetworkResponseOverrides,
) -> Json {
let _ = self
let result : Map[String, Json] = {}
result["fromCache"] = Json::boolean(overrides.from_cache)
result["headers"] = Json::array(overrides.headers)
result["mimeType"] = Json::string(overrides.mime_type)
result["protocol"] = Json::string(overrides.protocol)
result["status"] = Json::number(overrides.status.to_double())
result["statusText"] = Json::string(overrides.status_text)
match overrides.auth_challenges {
Some(auth_challenges) => result["authChallenges"] = auth_challenges
None => ()
}
make_object(result)
}
///|
fn BidiProtocol::synthetic_network_request_lookup_id(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> String? {
let _ = self
match params {
Some(Object(map)) =>
match map.get("request") {
Some(String(request_lookup_id)) => Some(request_lookup_id)
_ => {
self.send_error(
request_id, "invalid argument", "request must be a string",
)
None
}
}
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
None
}
}
}
///|
fn BidiProtocol::synthetic_network_cache_enabled(
self : BidiProtocol,
context_id : String,
) -> Bool {
self.synthetic_network_cache_behavior(context_id) == "default"
}
///|
fn BidiProtocol::synthetic_network_cache_behavior(
self : BidiProtocol,
context_id : String,
) -> String {
self.network_cache_behavior_by_context
.get(context_id)
.unwrap_or(self.network_cache_behavior_global)
}
///|
fn synthetic_network_header_entries_from_object(
raw_headers : Map[String, Json],
) -> Array[Json] {
let headers : Array[Json] = []
for name, raw_value in raw_headers {
headers.push(
synthetic_network_header_string_entry(
name,
synthetic_network_header_value_from_json(raw_value),
),
)
}
headers
}
///|
fn synthetic_network_merge_header_entries(headers : Array[Json]) -> Array[Json] {
let order : Array[String] = []
let names : Map[String, String] = {}
let values : Map[String, String] = {}
for header in headers {
let name = match synthetic_network_header_entry_name(header) {
Some(name) => name
None => continue
}
let value = match synthetic_network_header_entry_string_value(header) {
Some(value) => value
None => continue
}
let key = name.to_lower()
if values.contains(key) {
let merged = values.get(key).unwrap_or("")
values[key] = merged + ", " + value
} else {
order.push(key)
names[key] = name
values[key] = value
}
}
let merged : Array[Json] = []
for key in order {
let name = names.get(key).unwrap_or(key)
let value = values.get(key).unwrap_or("")
merged.push(synthetic_network_header_string_entry(name, value))
}
merged
}
///|
fn synthetic_network_header_entry_name(header : Json) -> String? {
match header {
Object(map) =>
match map.get("name") {
Some(String(name)) => Some(name)
_ => None
}
_ => None
}
}
///|
fn synthetic_network_header_entry_string_value(header : Json) -> String? {
match header {
Object(map) =>
match map.get("value") {
Some(Object(value_map)) =>
match value_map.get("value") {
Some(String(value)) => Some(value)
_ => None
}
_ => None
}
_ => None
}
}
///|
fn synthetic_network_header_value_from_json(raw_value : Json) -> String {
match raw_value {
String(value) => value
Number(value, ..) => value.to_string()
True => "true"
False => "false"
Null => ""
Array(_) | Object(_) => raw_value.stringify()
}
}
///|
fn synthetic_network_is_valid_continue_url(url : String) -> Bool {
if url.has_prefix("data:") {
return true
}
if url.has_prefix("http://") {
return has_valid_http_host(url, 7)
}
if url.has_prefix("https://") {
return has_valid_http_host(url, 8)
}
false
}
///|
fn synthetic_network_cache_key(http_method : String, url : String) -> String {
http_method + " " + url
}
///|
fn synthetic_network_request_path(url : String) -> String {
if url.has_prefix("http://") {
storage_http_path(url, 7)
} else if url.has_prefix("https://") {
storage_http_path(url, 8)
} else {
""
}
}
///|
fn synthetic_network_first_query_value(url : String, key : String) -> String? {
let values = synthetic_network_query_values(url, key)
if values.length() == 0 {
None
} else {
Some(values[0])
}
}
///|
fn synthetic_network_query_values(url : String, key : String) -> Array[String] {
let values : Array[String] = []
match find_substring(url, "?", 0) {
Some(query_idx) => {
let query_start = query_idx + 1
if query_start >= url.length() {
return values
}
let query = url.unsafe_substring(start=query_start, end=url.length())
for part in query.split("&") {
let raw = part.to_string()
if raw.length() == 0 {
continue
}
match find_substring(raw, "=", 0) {
Some(eq_idx) => {
let part_key = raw.unsafe_substring(start=0, end=eq_idx)
if part_key != key {
continue
}
let value_start = eq_idx + 1
let raw_value = if value_start <= raw.length() {
raw.unsafe_substring(start=value_start, end=raw.length())
} else {
""
}
values.push(js_decode_query_component(raw_value))
}
None => if raw == key { values.push("") }
}
}
values
}
None => values
}
}
///|
fn synthetic_network_int_query_param(
url : String,
key : String,
default_value : Int,
) -> Int {
match synthetic_network_first_query_value(url, key) {
Some(value) =>
match parse_decimal_string(value) {
Some(parsed) => parsed
None => default_value
}
None => default_value
}
}
///|
fn synthetic_network_request_header_string_value(
headers : Array[Json],
name : String,
) -> String? {
let lookup = name.to_lower()
for header in headers {
match header {
Object(map) => {
let header_name = match map.get("name") {
Some(String(header_name)) => header_name.to_lower()
_ => continue
}
if header_name != lookup {
continue
}
match map.get("value") {
Some(Object(value_map)) =>
match value_map.get("value") {
Some(String(value)) => return Some(value)
_ => ()
}
_ => ()
}
}
_ => ()
}
}
None
}
///|
fn synthetic_network_header_string_entry(name : String, value : String) -> Json {
make_object({
"name": Json::string(name),
"value": make_object({
"type": Json::string("string"),
"value": Json::string(value),
}),
})
}
///|
fn synthetic_network_infer_mime_type(url : String, path : String) -> String {
if url.has_prefix("data:") {
let payload_start = match find_substring(url, ",", 0) {
Some(payload_start) => payload_start
None => url.length()
}
let prefix = if payload_start > 5 {
url.unsafe_substring(start=5, end=payload_start)
} else {
""
}
match find_substring(prefix, ";", 0) {
Some(idx) => {
let mime_type = prefix.unsafe_substring(start=0, end=idx)
if mime_type == "" {
"text/plain"
} else {
mime_type.to_lower()
}
}
None => if prefix == "" { "text/plain" } else { prefix.to_lower() }
}
} else if path.has_suffix(".html") {
"text/html"
} else if path.has_suffix(".txt") {
"text/plain"
} else if path.has_suffix(".js") {
"text/javascript"
} else if path.has_suffix(".png") {
"image/png"
} else if path.has_suffix(".svg") {
"image/svg+xml"
} else if path.has_suffix(".css") {
"text/css"
} else if path.has_suffix("/cached.py") {
synthetic_network_first_query_value(url, "contenttype").unwrap_or(
"text/plain",
)
} else {
"text/plain"
}
}
///|
fn synthetic_network_status_text_for_code(status : Int) -> String {
match status {
101 => "Switching Protocols"
200 => "OK"
201 => "Created"
202 => "Accepted"
203 => "Non-Authoritative Information"
204 => "No Content"
205 => "Reset Content"
206 => "Partial Content"
300 => "Multiple Choices"
305 => "Use Proxy"
301 => "Moved Permanently"
302 => "Found"
303 => "See Other"
304 => "Not Modified"
307 => "Temporary Redirect"
308 => "Permanent Redirect"
400 => "Bad Request"
401 => "Unauthorized"
402 => "Payment Required"
403 => "Forbidden"
404 => "Not Found"
405 => "Method Not Allowed"
406 => "Not Acceptable"
407 => "Proxy Authentication Required"
408 => "Request Timeout"
409 => "Conflict"
410 => "Gone"
411 => "Length Required"
412 => "Precondition Failed"
415 => "Unsupported Media Type"
417 => "Expectation Failed"
500 => "Internal Server Error"
501 => "Not Implemented"
502 => "Bad Gateway"
503 => "Service Unavailable"
504 => "Gateway Timeout"
505 => "HTTP Version Not Supported"
_ => if status == 200 { "OK" } else { "" }
}
}