///|
fn default_geolocation_coordinates_json() -> Json {
make_object({
"latitude": Json::number(35.6895),
"longitude": Json::number(139.6917),
"accuracy": Json::number(1.0),
})
}
///|
fn build_geolocation_coordinates_override(coordinates : Json) -> Json {
make_object({
"kind": Json::string("coordinates"),
"coordinates": coordinates,
})
}
///|
fn build_geolocation_error_override(error_type : String) -> Json {
make_object({
"kind": Json::string("error"),
"error": make_object({ "type": Json::string(error_type) }),
})
}
///|
fn BidiProtocol::resolve_effective_geolocation_override(
self : BidiProtocol,
ctx_id : String,
) -> Json? {
let ancestry = self.storage_context_ancestry(ctx_id)
let mut index = ancestry.length()
while index > 0 {
index = index - 1
let scope_id = ancestry[index]
match self.emulation_geolocation_by_context.get(scope_id) {
Some(override_value) => return Some(override_value)
None => ()
}
}
let user_context_id = self.context_user_context
.get(ctx_id)
.unwrap_or("default")
match self.emulation_geolocation_by_user_context.get(user_context_id) {
Some(override_value) => Some(override_value)
None => self.emulation_geolocation_global
}
}
///|
fn BidiProtocol::resolve_effective_geolocation_error_code(
self : BidiProtocol,
ctx_id : String,
) -> Int? {
if self.resolve_permission_state_for_context("geolocation", ctx_id) !=
"granted" {
return Some(1)
}
match self.resolve_effective_geolocation_override(ctx_id) {
Some(Object(override_map)) =>
match override_map.get("kind") {
Some(String("error")) => Some(2)
_ => None
}
_ => None
}
}
///|
fn BidiProtocol::resolve_effective_geolocation_coordinates(
self : BidiProtocol,
ctx_id : String,
) -> Json? {
if self.resolve_permission_state_for_context("geolocation", ctx_id) !=
"granted" {
return None
}
match self.resolve_effective_geolocation_override(ctx_id) {
Some(Object(override_map)) =>
match (override_map.get("kind"), override_map.get("coordinates")) {
(Some(String("coordinates")), Some(Object(coords_map))) =>
Some(make_object(coords_map))
_ => None
}
None => Some(default_geolocation_coordinates_json())
_ => None
}
}
///|
fn push_geolocation_number_remote_entry(
entries : Array[Json],
coordinates_map : Map[String, Json],
key : String,
) -> Unit {
match coordinates_map.get(key) {
Some(Number(value, ..)) =>
entries.push(
Json::array([
Json::string(key),
make_object({
"type": Json::string("number"),
"value": Json::number(value),
}),
]),
)
_ => ()
}
}
///|
fn build_geolocation_coordinates_remote_value(coordinates : Json) -> Json {
let entries : Array[Json] = []
match coordinates {
Object(coordinates_map) => {
push_geolocation_number_remote_entry(entries, coordinates_map, "latitude")
push_geolocation_number_remote_entry(
entries, coordinates_map, "longitude",
)
push_geolocation_number_remote_entry(entries, coordinates_map, "accuracy")
push_geolocation_number_remote_entry(entries, coordinates_map, "altitude")
push_geolocation_number_remote_entry(
entries, coordinates_map, "altitudeAccuracy",
)
push_geolocation_number_remote_entry(entries, coordinates_map, "heading")
push_geolocation_number_remote_entry(entries, coordinates_map, "speed")
}
_ => ()
}
make_object({ "type": Json::string("object"), "value": Json::array(entries) })
}
///|
fn build_geolocation_error_remote_value(code : Int) -> Json {
make_object({
"type": Json::string("object"),
"value": Json::array([
Json::array([
Json::string("code"),
make_object({
"type": Json::string("number"),
"value": Json::number(code.to_double()),
}),
]),
]),
})
}
///|
fn geolocation_watch_scope_key(ctx_id : String, watch_id : Int) -> String {
ctx_id + "|" + watch_id.to_string()
}
///|
fn geolocation_watch_scope_context(scope_key : String) -> String {
match find_substring(scope_key, "|", 0) {
Some(separator) => scope_key.unsafe_substring(start=0, end=separator)
None => scope_key
}
}
///|
fn extract_script_channel_name(params : Json?) -> String? {
match get_param_raw(params, "arguments") {
Some(Array(arguments)) =>
for argument in arguments {
match argument {
Object(argument_map) =>
match (argument_map.get("type"), argument_map.get("value")) {
(Some(String("channel")), Some(Object(channel_map))) =>
match channel_map.get("channel") {
Some(String(channel_name)) =>
if channel_name != "" {
return Some(channel_name)
}
_ => ()
}
_ => ()
}
_ => ()
}
}
_ => ()
}
None
}
///|
fn get_first_numeric_argument(params : Json?) -> Double? {
match get_param_raw(params, "arguments") {
Some(Array(arguments)) =>
if arguments.length() > 0 {
remote_value_as_number(arguments[0])
} else {
None
}
_ => None
}
}
///|
fn BidiProtocol::emit_geolocation_watch_update_for_scope(
self : BidiProtocol,
scope_key : String,
) -> Unit {
let ctx_id = geolocation_watch_scope_context(scope_key)
let realm_id = match self.geolocation_watch_realm_by_scope.get(scope_key) {
Some(realm_id) => realm_id
None => return
}
let channel = match self.geolocation_watch_channel_by_scope.get(scope_key) {
Some(channel) => channel
None => return
}
match self.resolve_effective_geolocation_error_code(ctx_id) {
Some(code) =>
if self.geolocation_watch_notify_error_by_scope
.get(scope_key)
.unwrap_or(false) {
self.emit_script_message(
ctx_id,
realm_id,
channel,
build_geolocation_error_remote_value(code),
)
}
None =>
if self.geolocation_watch_notify_success_by_scope
.get(scope_key)
.unwrap_or(false) {
let coordinates = self
.resolve_effective_geolocation_coordinates(ctx_id)
.unwrap_or(default_geolocation_coordinates_json())
self.emit_script_message(
ctx_id,
realm_id,
channel,
build_geolocation_coordinates_remote_value(coordinates),
)
}
}
}
///|
fn BidiProtocol::notify_geolocation_watchers(self : BidiProtocol) -> Unit {
let watcher_keys : Array[String] = []
for scope_key, _ in self.geolocation_watch_channel_by_scope {
watcher_keys.push(scope_key)
}
for scope_key in watcher_keys {
self.emit_geolocation_watch_update_for_scope(scope_key)
}
}
///|
fn BidiProtocol::register_geolocation_watch(
self : BidiProtocol,
ctx_id : String,
realm_id : String,
channel : String,
notify_success : Bool,
notify_error : Bool,
) -> Int {
let watch_id = self.next_geolocation_watch_id
self.next_geolocation_watch_id = self.next_geolocation_watch_id + 1
let scope_key = geolocation_watch_scope_key(ctx_id, watch_id)
self.geolocation_watch_channel_by_scope[scope_key] = channel
self.geolocation_watch_realm_by_scope[scope_key] = realm_id
self.geolocation_watch_notify_success_by_scope[scope_key] = notify_success
self.geolocation_watch_notify_error_by_scope[scope_key] = notify_error
self.emit_geolocation_watch_update_for_scope(scope_key)
watch_id
}
///|
fn BidiProtocol::clear_geolocation_watch(
self : BidiProtocol,
ctx_id : String,
watch_id : Int,
) -> Unit {
let scope_key = geolocation_watch_scope_key(ctx_id, watch_id)
self.geolocation_watch_channel_by_scope.remove(scope_key)
self.geolocation_watch_realm_by_scope.remove(scope_key)
self.geolocation_watch_notify_success_by_scope.remove(scope_key)
self.geolocation_watch_notify_error_by_scope.remove(scope_key)
}
///|
fn normalize_geolocation_number_json(
value : Json,
min : Double,
max : Double,
) -> Double? {
json_as_safe_number(value, min, max)
}
///|
fn BidiProtocol::parse_geolocation_coordinates_param(
self : BidiProtocol,
request_id : Int,
raw_value : Json,
) -> (Bool, Json?) {
match raw_value {
Null => (true, None)
Object(coordinates_map) => {
let latitude = match coordinates_map.get("latitude") {
Some(value) =>
match normalize_geolocation_number_json(value, -90.0, 90.0) {
Some(parsed) => parsed
None => {
self.send_error(
request_id, "invalid argument", "coordinates.latitude is invalid",
)
return (false, None)
}
}
None => {
self.send_error(
request_id, "invalid argument", "coordinates.latitude must be provided",
)
return (false, None)
}
}
let longitude = match coordinates_map.get("longitude") {
Some(value) =>
match normalize_geolocation_number_json(value, -180.0, 180.0) {
Some(parsed) => parsed
None => {
self.send_error(
request_id, "invalid argument", "coordinates.longitude is invalid",
)
return (false, None)
}
}
None => {
self.send_error(
request_id, "invalid argument", "coordinates.longitude must be provided",
)
return (false, None)
}
}
let accuracy = match coordinates_map.get("accuracy") {
Some(value) =>
match
normalize_geolocation_number_json(value, 0.0, 9007199254740991.0) {
Some(parsed) => parsed
None => {
self.send_error(
request_id, "invalid argument", "coordinates.accuracy is invalid",
)
return (false, None)
}
}
None => 1.0
}
let altitude = match coordinates_map.get("altitude") {
Some(value) =>
match
normalize_geolocation_number_json(
value, -9007199254740991.0, 9007199254740991.0,
) {
Some(parsed) => Some(parsed)
None => {
self.send_error(
request_id, "invalid argument", "coordinates.altitude is invalid",
)
return (false, None)
}
}
None => None
}
let altitude_accuracy = match
get_map_field_with_alias(
coordinates_map, "altitudeAccuracy", "altitude_accuracy",
) {
Some(value) =>
match
normalize_geolocation_number_json(value, 0.0, 9007199254740991.0) {
Some(parsed) => Some(parsed)
None => {
self.send_error(
request_id, "invalid argument", "coordinates.altitudeAccuracy is invalid",
)
return (false, None)
}
}
None => None
}
if altitude_accuracy is Some(_) && altitude is None {
self.send_error(
request_id, "invalid argument", "coordinates.altitudeAccuracy requires altitude",
)
return (false, None)
}
let heading = match coordinates_map.get("heading") {
Some(value) =>
match normalize_geolocation_number_json(value, 0.0, 359.999999999) {
Some(parsed) => Some(parsed)
None => {
self.send_error(
request_id, "invalid argument", "coordinates.heading is invalid",
)
return (false, None)
}
}
None => None
}
let speed = match coordinates_map.get("speed") {
Some(value) =>
match
normalize_geolocation_number_json(value, 0.0, 9007199254740991.0) {
Some(parsed) => Some(parsed)
None => {
self.send_error(
request_id, "invalid argument", "coordinates.speed is invalid",
)
return (false, None)
}
}
None => None
}
let normalized : Map[String, Json] = {}
normalized["latitude"] = Json::number(latitude)
normalized["longitude"] = Json::number(longitude)
normalized["accuracy"] = Json::number(accuracy)
match altitude {
Some(parsed) => normalized["altitude"] = Json::number(parsed)
None => ()
}
match altitude_accuracy {
Some(parsed) => normalized["altitudeAccuracy"] = Json::number(parsed)
None => ()
}
match heading {
Some(parsed) => normalized["heading"] = Json::number(parsed)
None => ()
}
match speed {
Some(parsed) => normalized["speed"] = Json::number(parsed)
None => ()
}
(true, Some(make_object(normalized)))
}
_ => {
self.send_error(
request_id, "invalid argument", "coordinates must be an object or null",
)
(false, None)
}
}
}
///|
fn BidiProtocol::parse_geolocation_error_param(
self : BidiProtocol,
request_id : Int,
raw_value : Json,
) -> (Bool, String?) {
match raw_value {
Null => (true, None)
Object(error_map) =>
match error_map.get("type") {
Some(String("positionUnavailable")) =>
(true, Some("positionUnavailable"))
Some(String(_)) => {
self.send_error(
request_id, "invalid argument", "error.type is invalid",
)
(false, None)
}
_ => {
self.send_error(
request_id, "invalid argument", "error.type must be provided",
)
(false, None)
}
}
_ => {
self.send_error(
request_id, "invalid argument", "error must be an object or null",
)
(false, None)
}
}
}
///|
fn BidiProtocol::resolve_emulation_set_geolocation_override(
self : BidiProtocol,
request_id : Int,
params : Json?,
) -> Bool? {
let map = match params {
Some(Object(map)) => map
_ => {
self.send_error(
request_id, "invalid argument", "params must be an object",
)
return None
}
}
let has_coordinates = map.contains("coordinates")
let has_error = map.contains("error")
if !has_coordinates && !has_error {
self.send_error(
request_id, "invalid argument", "coordinates or error must be provided",
)
return None
}
let coordinates_override : Json? = if has_coordinates {
let raw_value = map.get("coordinates").unwrap_or(Json::null())
let (ok, normalized_coordinates) = self.parse_geolocation_coordinates_param(
request_id, raw_value,
)
if !ok {
return None
}
match normalized_coordinates {
Some(normalized_coordinates) =>
Some(build_geolocation_coordinates_override(normalized_coordinates))
None => None
}
} else {
None
}
let error_override : Json? = if has_error {
let raw_value = map.get("error").unwrap_or(Json::null())
let (ok, error_type) = self.parse_geolocation_error_param(
request_id, raw_value,
)
if !ok {
return None
}
match error_type {
Some(error_type) => Some(build_geolocation_error_override(error_type))
None => None
}
} else {
None
}
if coordinates_override is Some(_) && error_override is Some(_) {
self.send_error(
request_id, "invalid argument", "coordinates and error are mutually exclusive",
)
return None
}
let override_value = match coordinates_override {
Some(value) => Some(value)
None => error_override
}
let target_contexts : Array[String] = []
match map.get("contexts") {
Some(Array(contexts)) =>
if contexts.length() == 0 {
self.send_error(
request_id, "invalid argument", "contexts must not be an empty array",
)
return None
} else {
for context in contexts {
match context {
String(ctx_id) =>
if !self.manager.has_session(ctx_id) {
self.send_error(
request_id,
"no such frame",
"Unknown context: " + ctx_id,
)
return None
} else if self.context_parent.contains(ctx_id) {
self.send_error(
request_id, "invalid argument", "contexts must reference top-level browsing contexts",
)
return None
} else if !array_contains(target_contexts, ctx_id) {
target_contexts.push(ctx_id)
}
_ => {
self.send_error(
request_id, "invalid argument", "contexts entries must be strings",
)
return None
}
}
}
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "contexts must be an array",
)
return None
}
None => ()
}
let target_user_contexts : Array[String] = []
match get_map_field_with_alias(map, "userContexts", "user_contexts") {
Some(Array(user_contexts)) =>
if user_contexts.length() == 0 {
self.send_error(
request_id, "invalid argument", "userContexts must not be an empty array",
)
return None
} else {
for raw_user_context in user_contexts {
match raw_user_context {
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 None
} else if !array_contains(target_user_contexts, user_context_id) {
target_user_contexts.push(user_context_id)
}
_ => {
self.send_error(
request_id, "invalid argument", "userContexts entries must be strings",
)
return None
}
}
}
}
Some(_) => {
self.send_error(
request_id, "invalid argument", "userContexts must be an array",
)
return None
}
None => ()
}
if target_contexts.length() > 0 && target_user_contexts.length() > 0 {
self.send_error(
request_id, "invalid argument", "contexts and userContexts are mutually exclusive",
)
return None
}
if target_contexts.length() > 0 {
for ctx_id in target_contexts {
match override_value {
Some(override_value) =>
self.emulation_geolocation_by_context[ctx_id] = override_value
None => self.emulation_geolocation_by_context.remove(ctx_id)
}
}
self.notify_geolocation_watchers()
return Some(true)
}
if target_user_contexts.length() > 0 {
for user_context_id in target_user_contexts {
match override_value {
Some(override_value) =>
self.emulation_geolocation_by_user_context[user_context_id] = override_value
None =>
self.emulation_geolocation_by_user_context.remove(user_context_id)
}
}
self.notify_geolocation_watchers()
return Some(true)
}
self.emulation_geolocation_global = override_value
self.notify_geolocation_watchers()
Some(true)
}
///|
fn BidiProtocol::try_handle_synthetic_geolocation_call(
self : BidiProtocol,
request_id : Int,
ctx_id : String,
realm_id : String,
function_declaration : String,
params : Json?,
unwrap_result : Bool,
) -> Bool {
if function_declaration.contains("navigator.geolocation.clearWatch(") {
let watch_id = match get_first_numeric_argument(params) {
Some(value) => value.to_int()
None => 0
}
self.clear_geolocation_watch(ctx_id, watch_id)
self.send_script_undefined_response(request_id, realm_id, unwrap_result)
return true
}
if function_declaration.contains("navigator.geolocation.watchPosition(") {
let channel_name = match extract_script_channel_name(params) {
Some(channel_name) => channel_name
None => return false
}
let notify_success = function_declaration.contains("result.coords.toJSON()")
let notify_error = function_declaration.contains("error.code")
if !notify_success && !notify_error {
return false
}
let watch_id = self.register_geolocation_watch(
ctx_id, realm_id, channel_name, notify_success, notify_error,
)
self.send_script_remote_value_response(
request_id,
realm_id,
make_object({
"type": Json::string("number"),
"value": Json::number(watch_id.to_double()),
}),
unwrap_result,
)
return true
}
if !function_declaration.contains("navigator.geolocation.getCurrentPosition(") {
return false
}
match self.resolve_effective_geolocation_error_code(ctx_id) {
Some(code) =>
self.send_script_remote_value_response(
request_id,
realm_id,
build_geolocation_error_remote_value(code),
unwrap_result,
)
None => {
let coordinates = self
.resolve_effective_geolocation_coordinates(ctx_id)
.unwrap_or(default_geolocation_coordinates_json())
self.send_script_remote_value_response(
request_id,
realm_id,
build_geolocation_coordinates_remote_value(coordinates),
unwrap_result,
)
}
}
true
}