diff --git a/core/engine/src/builtins/map/mod.rs b/core/engine/src/builtins/map/mod.rs index 40223620e8f..8c8c05bff2b 100644 --- a/core/engine/src/builtins/map/mod.rs +++ b/core/engine/src/builtins/map/mod.rs @@ -501,6 +501,53 @@ impl Map { } } + /// Call `f` for each `(key, value)` in the `Map`. + /// + /// Can not be used in [`Self::for_each`] because in that case will be + /// incorrect order for next steps of the algo: + /// ```txt + /// 2. Perform ? RequireInternalSlot(M, [[MapData]]). + /// 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + /// ``` + pub(crate) fn for_each_native(this: &JsValue, mut f: F) -> JsResult<()> + where + F: FnMut(JsValue, JsValue) -> JsResult<()>, + { + // See `Self::for_each` for comments on the algo. + + let map = this + .as_object() + .filter(|obj| obj.is::>()) + .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a Map"))?; + + let _lock = map + .downcast_mut::>() + .expect("checked that `this` was a map") + .lock(map.clone()); + + let mut index = 0; + loop { + let (k, v) = { + let map = map + .downcast_ref::>() + .expect("checked that `this` was a map"); + + if index < map.full_len() { + if let Some((k, v)) = map.get_index(index) { + (k.clone(), v.clone()) + } else { + continue; + } + } else { + return Ok(()); + } + }; + + f(k, v)?; + index += 1; + } + } + /// `Map.prototype.values()` /// /// Returns a new Iterator object that contains the values for each element in the Map object in insertion order. diff --git a/core/engine/src/object/builtins/jsmap.rs b/core/engine/src/object/builtins/jsmap.rs index 7027bbaa46a..af63ef90db5 100644 --- a/core/engine/src/object/builtins/jsmap.rs +++ b/core/engine/src/object/builtins/jsmap.rs @@ -402,6 +402,16 @@ impl JsMap { ) } + /// Executes the provided callback function for each key-value pair within the [`JsMap`]. + #[inline] + pub fn for_each_native(&self, f: F) -> JsResult<()> + where + F: FnMut(JsValue, JsValue) -> JsResult<()>, + { + let this = self.inner.clone().into(); + Map::for_each_native(&this, f) + } + /// Returns a new [`JsMapIterator`] object that yields the `value` for each element within the [`JsMap`] in insertion order. #[inline] pub fn values(&self, context: &mut Context) -> JsResult { diff --git a/core/engine/src/value/conversions/try_from_js.rs b/core/engine/src/value/conversions/try_from_js.rs index b41645d0f36..b1806f8311c 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 = [("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..4934de30352 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,7 @@ use std::collections::{BTreeMap, HashMap}; use std::hash::Hash; +use crate::object::JsMap; use crate::value::TryFromJs; use crate::{Context, JsNativeError, JsResult, JsValue}; @@ -18,6 +19,20 @@ where .into()); }; + // JsMap case + if let Ok(js_map) = JsMap::from_object(object.clone()) { + let mut map = Self::default(); + js_map.for_each_native(|key, value| { + map.insert( + K::try_from_js(&key, context)?, + V::try_from_js(&value, context)?, + ); + Ok(()) + })?; + return Ok(map); + } + + // key-valued JsObject case: let keys = object.__own_property_keys__(context)?; keys.into_iter() @@ -47,6 +62,20 @@ where .into()); }; + // JsMap case + if let Ok(js_map) = JsMap::from_object(object.clone()) { + let mut map = Self::default(); + js_map.for_each_native(|key, value| { + map.insert( + K::try_from_js(&key, context)?, + V::try_from_js(&value, context)?, + ); + Ok(()) + })?; + return Ok(map); + } + + // key-valued JsObject case: let keys = object.__own_property_keys__(context)?; keys.into_iter()