diff --git a/core/engine/src/value/conversions/try_from_js.rs b/core/engine/src/value/conversions/try_from_js.rs index b41645d0f36..cb1b66c48b4 100644 --- a/core/engine/src/value/conversions/try_from_js.rs +++ b/core/engine/src/value/conversions/try_from_js.rs @@ -559,3 +559,26 @@ fn value_into_map() { }), ]); } + +#[test] +fn js_map_into_rust_map() -> JsResult<()> { + use boa_engine::Source; + use std::collections::{BTreeMap, HashMap}; + + let js_code = "new Map([['a', 1], ['b', 3], ['aboba', 42024]])"; + let mut context = Context::default(); + + let js_value = context.eval(Source::from_bytes(js_code))?; + + let hash_map = HashMap::::try_from_js(&js_value, &mut context)?; + let btree_map = BTreeMap::::try_from_js(&js_value, &mut context)?; + + let expect = vec![("a".into(), 1), ("aboba".into(), 42024), ("b".into(), 3)]; + + let expected_hash_map: HashMap = expect.iter().cloned().collect(); + assert_eq!(expected_hash_map, hash_map); + + let expected_btree_map: BTreeMap = expect.iter().cloned().collect(); + assert_eq!(expected_btree_map, btree_map); + Ok(()) +} diff --git a/core/engine/src/value/conversions/try_from_js/collections.rs b/core/engine/src/value/conversions/try_from_js/collections.rs index 37d649e9630..5c8e5a1b65e 100644 --- a/core/engine/src/value/conversions/try_from_js/collections.rs +++ b/core/engine/src/value/conversions/try_from_js/collections.rs @@ -3,6 +3,9 @@ use std::collections::{BTreeMap, HashMap}; use std::hash::Hash; +use boa_macros::js_str; + +use crate::object::{JsArray, JsMap}; use crate::value::TryFromJs; use crate::{Context, JsNativeError, JsResult, JsValue}; @@ -18,6 +21,21 @@ where .into()); }; + // JsMap case + if let Ok(js_map) = JsMap::from_object(object.clone()) { + let mut map = Self::default(); + let f = |key, value, context: &mut _| { + map.insert( + K::try_from_js(&key, context)?, + V::try_from_js(&value, context)?, + ); + Ok(()) + }; + for_each_elem_in_js_map(&js_map, f, context)?; + return Ok(map); + } + + // key-valued JsObject case: let keys = object.__own_property_keys__(context)?; keys.into_iter() @@ -47,6 +65,21 @@ where .into()); }; + // JsMap case + if let Ok(js_map) = JsMap::from_object(object.clone()) { + let mut map = Self::default(); + let f = |key, value, context: &mut _| { + map.insert( + K::try_from_js(&key, context)?, + V::try_from_js(&value, context)?, + ); + Ok(()) + }; + for_each_elem_in_js_map(&js_map, f, context)?; + return Ok(map); + } + + // key-valued JsObject case: let keys = object.__own_property_keys__(context)?; keys.into_iter() @@ -62,3 +95,44 @@ where .collect() } } + +fn for_each_elem_in_js_map(js_map: &JsMap, mut f: F, context: &mut Context) -> JsResult<()> +where + F: FnMut(JsValue, JsValue, &mut Context) -> JsResult<()>, +{ + let unexp_obj_err = || { + JsResult::Err( + JsNativeError::typ() + .with_message("MapIterator return unexpected object") + .into(), + ) + }; + + let iter = js_map.entries(context)?; + loop { + let next = iter.next(context)?; + let Some(iter_obj) = next.as_object() else { + return unexp_obj_err(); + }; + + let done = iter_obj.get(js_str!("done"), context)?; + let Some(done) = done.as_boolean() else { + return unexp_obj_err(); + }; + if done { + break; + } + + let value = iter_obj.get(js_str!("value"), context)?; + let Some(js_obj) = value.as_object() else { + return unexp_obj_err(); + }; + let arr = JsArray::from_object(js_obj.clone())?; + + let key = arr.at(0, context)?; + let value = arr.at(1, context)?; + + f(key, value, context)?; + } + Ok(()) +}