boa/builtins/object/
for_in_iterator.rs

1use crate::{
2    builtins::{function::make_builtin_fn, iterable::create_iter_result_object},
3    gc::{Finalize, Trace},
4    object::{JsObject, ObjectData},
5    property::PropertyDescriptor,
6    property::PropertyKey,
7    symbol::WellKnownSymbols,
8    BoaProfiler, Context, JsResult, JsString, JsValue,
9};
10use rustc_hash::FxHashSet;
11use std::collections::VecDeque;
12
13/// The ForInIterator object represents an iteration over some specific object.
14/// It implements the iterator protocol.
15///
16/// More information:
17///  - [ECMAScript reference][spec]
18///
19/// [spec]: https://tc39.es/ecma262/#sec-for-in-iterator-objects
20#[derive(Debug, Clone, Finalize, Trace)]
21pub struct ForInIterator {
22    object: JsValue,
23    visited_keys: FxHashSet<JsString>,
24    remaining_keys: VecDeque<JsString>,
25    object_was_visited: bool,
26}
27
28impl ForInIterator {
29    pub(crate) const NAME: &'static str = "ForInIterator";
30
31    fn new(object: JsValue) -> Self {
32        ForInIterator {
33            object,
34            visited_keys: FxHashSet::default(),
35            remaining_keys: VecDeque::default(),
36            object_was_visited: false,
37        }
38    }
39
40    /// CreateForInIterator( object )
41    ///
42    /// Creates a new iterator over the given object.
43    ///
44    /// More information:
45    ///  - [ECMA reference][spec]
46    ///
47    /// [spec]: https://tc39.es/ecma262/#sec-createforiniterator
48    pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context) -> JsValue {
49        let for_in_iterator = JsValue::new_object(context);
50        for_in_iterator.set_data(ObjectData::for_in_iterator(Self::new(object)));
51        for_in_iterator
52            .as_object()
53            .expect("for in iterator object")
54            .set_prototype_instance(context.iterator_prototypes().for_in_iterator().into());
55        for_in_iterator
56    }
57
58    /// %ForInIteratorPrototype%.next( )
59    ///
60    /// Gets the next result in the object.
61    ///
62    /// More information:
63    ///  - [ECMA reference][spec]
64    ///
65    /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next
66    pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
67        if let JsValue::Object(ref o) = this {
68            let mut for_in_iterator = o.borrow_mut();
69            if let Some(iterator) = for_in_iterator.as_for_in_iterator_mut() {
70                let mut object = iterator.object.to_object(context)?;
71                loop {
72                    if !iterator.object_was_visited {
73                        let keys = object.__own_property_keys__(context)?;
74                        for k in keys {
75                            match k {
76                                PropertyKey::String(ref k) => {
77                                    iterator.remaining_keys.push_back(k.clone());
78                                }
79                                PropertyKey::Index(i) => {
80                                    iterator.remaining_keys.push_back(i.to_string().into());
81                                }
82                                _ => {}
83                            }
84                        }
85                        iterator.object_was_visited = true;
86                    }
87                    while let Some(r) = iterator.remaining_keys.pop_front() {
88                        if !iterator.visited_keys.contains(&r) {
89                            if let Some(desc) = object
90                                .__get_own_property__(&PropertyKey::from(r.clone()), context)?
91                            {
92                                iterator.visited_keys.insert(r.clone());
93                                if desc.expect_enumerable() {
94                                    return Ok(create_iter_result_object(
95                                        JsValue::new(r.to_string()),
96                                        false,
97                                        context,
98                                    ));
99                                }
100                            }
101                        }
102                    }
103                    match object.prototype_instance().to_object(context) {
104                        Ok(o) => {
105                            object = o;
106                        }
107                        _ => {
108                            return Ok(create_iter_result_object(
109                                JsValue::undefined(),
110                                true,
111                                context,
112                            ))
113                        }
114                    }
115                    iterator.object = JsValue::new(object.clone());
116                    iterator.object_was_visited = false;
117                }
118            } else {
119                context.throw_type_error("`this` is not a ForInIterator")
120            }
121        } else {
122            context.throw_type_error("`this` is not an ForInIterator")
123        }
124    }
125
126    /// Create the %ArrayIteratorPrototype% object
127    ///
128    /// More information:
129    ///  - [ECMA reference][spec]
130    ///
131    /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%-object
132    pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
133        let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
134
135        // Create prototype
136        let for_in_iterator = context.construct_object();
137        make_builtin_fn(Self::next, "next", &for_in_iterator, 0, context);
138        for_in_iterator.set_prototype_instance(iterator_prototype);
139
140        let to_string_tag = WellKnownSymbols::to_string_tag();
141        let to_string_tag_property = PropertyDescriptor::builder()
142            .value("For In Iterator")
143            .writable(false)
144            .enumerable(false)
145            .configurable(true);
146        for_in_iterator.insert(to_string_tag, to_string_tag_property);
147        for_in_iterator
148    }
149}