///|
fn BidiProtocol::handle_browsing_context_create_synthetic_child_context(
  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
    None =>
      match get_map_field_with_alias(map, "parentContext", "parent_context") {
        Some(String(context_id)) => context_id
        _ => ""
      }
    _ => {
      self.send_error(
        request_id, "invalid argument", "parentContext must be a string",
      )
      return
    }
  }
  if context_id == "" {
    self.send_error(
      request_id, "invalid argument", "parentContext must be a string",
    )
    return
  }
  if !self.manager.has_session(context_id) {
    self.send_error(
      request_id,
      "no such frame",
      "Unknown parent context: " + context_id,
    )
    return
  }
  let user_context_id = match
    get_map_field_with_alias(map, "userContext", "user_context") {
    Some(String(user_context_id)) =>
      if user_context_id == "" || !self.user_contexts.contains(user_context_id) {
        self.send_error(
          request_id,
          "no such user context",
          "Unknown user context: " + user_context_id,
        )
        return
      } else {
        user_context_id
      }
    Some(Null) | None =>
      self.context_user_context.get(context_id).unwrap_or("default")
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "userContext must be a string",
      )
      return
    }
  }
  let requested_url = match map.get("url") {
    Some(String(url)) => url
    Some(Null) | None => "about:blank"
    Some(_) => {
      self.send_error(request_id, "invalid argument", "url must be a string")
      return
    }
  }

  let result = self.create_synthetic_child_context_result(
    context_id, requested_url, user_context_id,
  )
  self.send_success(request_id, Some(result))
}

///|
fn BidiProtocol::create_synthetic_child_context_result(
  self : BidiProtocol,
  context_id : String,
  requested_url : String,
  user_context_id : String,
) -> Json {
  let child_ctx_id = self.create_child_context(context_id, user_context_id)
  self.context_had_synthetic_child[context_id] = true
  let resolved_url = self.resolve_navigation_target_url(
    context_id, requested_url,
  )
  let effective_url = self.normalize_wpt_navigation_target_url(
    child_ctx_id, resolved_url,
  )

  self.emit_context_created(
    child_ctx_id,
    "tab",
    "about:blank",
    None,
    Some(context_id),
  )
  let child_nav_id = self.next_navigation_id.to_string()
  self.next_navigation_id += 1
  if self.is_iframe_navigation_blocked(child_ctx_id, effective_url) {
    self.emit_default_realm(child_ctx_id)
    self.emit_navigation_started_event(
      child_ctx_id, effective_url, child_nav_id,
    )
    self.emit_navigation_failed_event(child_ctx_id, effective_url, child_nav_id)
    return make_object({
      "context": Json::string(child_ctx_id),
      "parentContext": Json::string(context_id),
      "userContext": Json::string(user_context_id),
      "url": Json::string(effective_url),
    })
  }
  match self.manager.get_session(child_ctx_id) {
    Some(child_session) => {
      let _ = child_session.navigate_to(effective_url)
    }
    None => ()
  }
  self.load_data_url_content(child_ctx_id, effective_url)
  self.clear_context_sandbox_realms(child_ctx_id)
  let window_realm_id = self.assign_new_realm(child_ctx_id)
  self.emit_default_realm(child_ctx_id)
  self.emit_worker_realms_from_scripts(child_ctx_id, window_realm_id)
  self.apply_preload_scripts_for_context(child_ctx_id)
  self.execute_inline_scripts_for_context(child_ctx_id)
  self.emit_navigation_events(child_ctx_id, effective_url, child_nav_id)
  if effective_url != "about:blank" {
    self.refresh_child_contexts_from_iframes_with_depth(child_ctx_id, 1)
  }
  make_object({
    "context": Json::string(child_ctx_id),
    "parentContext": Json::string(context_id),
    "userContext": Json::string(user_context_id),
    "url": Json::string(effective_url),
  })
}

///|
fn BidiProtocol::handle_browsing_context_create_and_get_info(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> Unit {
  match self.create_top_level_context_from_params(request_id, params) {
    Some(ctx_id) =>
      match self.build_context_entry(ctx_id, true, 0, Some(0)) {
        Some(entry) => self.send_success(request_id, Some(entry))
        None =>
          self.send_error(
            request_id,
            "no such frame",
            "Unknown context: " + ctx_id,
          )
      }
    None => ()
  }
}

///|
fn BidiProtocol::handle_browsing_context_create_and_get_info_value(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> Unit {
  match self.create_top_level_context_from_params(request_id, params) {
    Some(ctx_id) =>
      match self.build_context_entry(ctx_id, true, 0, Some(0)) {
        Some(entry) => self.send_success(request_id, Some(entry))
        None =>
          self.send_error(
            request_id,
            "no such frame",
            "Unknown context: " + ctx_id,
          )
      }
    None => ()
  }
}

///|
fn BidiProtocol::handle_browsing_context_create_context_id(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> Unit {
  match self.create_top_level_context_from_params(request_id, params) {
    Some(ctx_id) => self.send_success(request_id, Some(Json::string(ctx_id)))
    None => ()
  }
}

///|
fn BidiProtocol::create_top_level_context_from_params(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> String? {
  let map = match params {
    Some(Object(map)) => map
    _ => {
      self.send_error(
        request_id, "invalid argument", "params must be an object",
      )
      return None
    }
  }

  let type_hint = match get_map_field_with_alias(map, "type", "type_hint") {
    Some(String(kind)) => kind
    Some(_) => {
      self.send_error(request_id, "invalid argument", "type must be a string")
      return None
    }
    None => "tab"
  }
  if type_hint != "tab" && type_hint != "window" {
    self.send_error(
      request_id, "invalid argument", "type must be 'tab' or 'window'",
    )
    return None
  }

  let background = match map.get("background") {
    Some(True) => true
    Some(False) => false
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "background must be a boolean",
      )
      return None
    }
    None => false
  }

  let reference_context = match
    get_map_field_with_alias(map, "referenceContext", "reference_context") {
    Some(String(reference_ctx_id)) => {
      if !self.manager.has_session(reference_ctx_id) {
        self.send_error(
          request_id,
          "no such frame",
          "Unknown context: " + reference_ctx_id,
        )
        return None
      }
      if self.context_parent.contains(reference_ctx_id) {
        self.send_error(
          request_id, "invalid argument", "referenceContext must be top-level",
        )
        return None
      }
      Some(reference_ctx_id)
    }
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "referenceContext must be a string",
      )
      return None
    }
    None => None
  }

  let requested_user_context = match
    get_map_field_with_alias(map, "userContext", "user_context") {
    Some(String(user_ctx_id)) =>
      if user_ctx_id == "" || !self.user_contexts.contains(user_ctx_id) {
        self.send_error(
          request_id,
          "no such user context",
          "Unknown user context: " + user_ctx_id,
        )
        return None
      } else {
        Some(user_ctx_id)
      }
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "userContext must be a string",
      )
      return None
    }
    None => None
  }

  let user_ctx = match requested_user_context {
    Some(user_ctx_id) => user_ctx_id
    None =>
      match reference_context {
        Some(reference_ctx_id) =>
          self.context_user_context.get(reference_ctx_id).unwrap_or("default")
        None => "default"
      }
  }

  let previous_active_context = self.active_context_id
  let ctx_id = self.create_context(user_ctx, type_hint)
  if type_hint == "window" {
    match reference_context {
      Some(reference_ctx_id) =>
        match self.context_client_window.get(reference_ctx_id) {
          Some(reference_client_window) =>
            self.context_client_window[ctx_id] = reference_client_window
          None => ()
        }
      None => ()
    }
  }
  if background {
    self.active_context_id = previous_active_context
  }
  self.emit_context_created(ctx_id, type_hint, "about:blank", None, None)
  self.emit_default_realm(ctx_id)
  self.apply_preload_scripts_for_context(ctx_id)
  Some(ctx_id)
}