Skip to main content

rquickjs_core/value/
object.rs

1//! Module for types dealing with JS objects.
2
3use crate::{
4    convert::FromIteratorJs, qjs, Array, Atom, Ctx, FromAtom, FromJs, IntoAtom, IntoJs, Result,
5    Value,
6};
7use core::{iter::FusedIterator, marker::PhantomData, mem};
8
9mod property;
10pub use property::{Accessor, AsProperty, Property, PropertyFlags};
11
12/// Rust representation of a JavaScript object.
13#[derive(Debug, PartialEq, Clone, Hash, Eq)]
14#[repr(transparent)]
15pub struct Object<'js>(pub(crate) Value<'js>);
16
17impl<'js> Object<'js> {
18    /// Create a new JavaScript object
19    pub fn new(ctx: Ctx<'js>) -> Result<Self> {
20        Ok(unsafe {
21            let val = qjs::JS_NewObject(ctx.as_ptr());
22            let val = ctx.handle_exception(val)?;
23            Object::from_js_value(ctx, val)
24        })
25    }
26
27    /// Create a new JavaScript object with a specified prototype
28    ///
29    /// This is equivalent to `Object.create(proto)` in JavaScript.
30    /// Pass `None` to create an object with a null prototype.
31    pub fn new_proto(ctx: Ctx<'js>, proto: Option<&Object<'js>>) -> Result<Self> {
32        let proto_val = proto.map(|p| p.0.as_js_value()).unwrap_or(qjs::JS_NULL);
33        Ok(unsafe {
34            let val = qjs::JS_NewObjectProto(ctx.as_ptr(), proto_val);
35            let val = ctx.handle_exception(val)?;
36            Object::from_js_value(ctx, val)
37        })
38    }
39
40    /// Get a new value
41    pub fn get<K: IntoAtom<'js>, V: FromJs<'js>>(&self, k: K) -> Result<V> {
42        let atom = k.into_atom(self.ctx())?;
43        V::from_js(self.ctx(), unsafe {
44            let val = qjs::JS_GetProperty(self.0.ctx.as_ptr(), self.0.as_js_value(), atom.atom);
45            let val = self.0.ctx.handle_exception(val)?;
46            Value::from_js_value(self.0.ctx.clone(), val)
47        })
48    }
49
50    /// check whether the object contains a certain key.
51    pub fn contains_key<K>(&self, k: K) -> Result<bool>
52    where
53        K: IntoAtom<'js>,
54    {
55        let atom = k.into_atom(self.ctx())?;
56        unsafe {
57            let res = qjs::JS_HasProperty(self.0.ctx.as_ptr(), self.0.as_js_value(), atom.atom);
58            if res < 0 {
59                return Err(self.0.ctx.raise_exception());
60            }
61            Ok(res == 1)
62        }
63    }
64
65    /// Set a member of an object to a certain value
66    pub fn set<K: IntoAtom<'js>, V: IntoJs<'js>>(&self, key: K, value: V) -> Result<()> {
67        let atom = key.into_atom(self.ctx())?;
68        let val = value.into_js(self.ctx())?;
69        unsafe {
70            if qjs::JS_SetProperty(
71                self.0.ctx.as_ptr(),
72                self.0.as_js_value(),
73                atom.atom,
74                val.into_js_value(),
75            ) < 0
76            {
77                return Err(self.0.ctx.raise_exception());
78            }
79        }
80        Ok(())
81    }
82
83    /// Remove a member of an object
84    pub fn remove<K: IntoAtom<'js>>(&self, key: K) -> Result<()> {
85        let atom = key.into_atom(self.ctx())?;
86        unsafe {
87            if qjs::JS_DeleteProperty(
88                self.0.ctx.as_ptr(),
89                self.0.as_js_value(),
90                atom.atom,
91                qjs::JS_PROP_THROW as _,
92            ) < 0
93            {
94                return Err(self.0.ctx.raise_exception());
95            }
96        }
97        Ok(())
98    }
99
100    /// Check the object for empty
101    pub fn is_empty(&self) -> bool {
102        self.keys::<Atom>().next().is_none()
103    }
104
105    /// Get the number of properties
106    pub fn len(&self) -> usize {
107        self.keys::<Atom>().count()
108    }
109
110    /// Get own string enumerable property names of an object
111    pub fn keys<K: FromAtom<'js>>(&self) -> ObjectKeysIter<'js, K> {
112        self.own_keys(Filter::default())
113    }
114
115    /// Get own property names of an object
116    pub fn own_keys<K: FromAtom<'js>>(&self, filter: Filter) -> ObjectKeysIter<'js, K> {
117        ObjectKeysIter {
118            state: Some(IterState::new(&self.0, filter.flags)),
119            marker: PhantomData,
120        }
121    }
122
123    /// Get own string enumerable properties of an object
124    pub fn props<K: FromAtom<'js>, V: FromJs<'js>>(&self) -> ObjectIter<'js, K, V> {
125        self.own_props(Filter::default())
126    }
127
128    /// Get own properties of an object
129    pub fn own_props<K: FromAtom<'js>, V: FromJs<'js>>(
130        &self,
131        filter: Filter,
132    ) -> ObjectIter<'js, K, V> {
133        ObjectIter {
134            state: Some(IterState::new(&self.0, filter.flags)),
135            object: self.clone(),
136            marker: PhantomData,
137        }
138    }
139
140    /// Get own string enumerable property values of an object
141    pub fn values<K: FromAtom<'js>>(&self) -> ObjectValuesIter<'js, K> {
142        self.own_values(Filter::default())
143    }
144
145    /// Get own property values of an object
146    pub fn own_values<K: FromAtom<'js>>(&self, filter: Filter) -> ObjectValuesIter<'js, K> {
147        ObjectValuesIter {
148            state: Some(IterState::new(&self.0, filter.flags)),
149            object: self.clone(),
150            marker: PhantomData,
151        }
152    }
153
154    /// Get an object prototype
155    ///
156    /// Objects can have no prototype, in this case this function will return null.
157    pub fn get_prototype(&self) -> Option<Object<'js>> {
158        unsafe {
159            let proto = qjs::JS_GetPrototype(self.0.ctx.as_ptr(), self.0.as_js_value());
160            if qjs::JS_IsNull(proto) {
161                None
162            } else {
163                Some(Object::from_js_value(self.0.ctx.clone(), proto))
164            }
165        }
166    }
167
168    /// Set an object prototype
169    ///
170    /// If called with None the function will set the prototype of the object to null.
171    ///
172    /// This function will error if setting the prototype causes a cycle in the prototype chain.
173    pub fn set_prototype(&self, proto: Option<&Object<'js>>) -> Result<()> {
174        let proto = proto.map(|x| x.as_js_value()).unwrap_or(qjs::JS_NULL);
175        unsafe {
176            if 1 != qjs::JS_SetPrototype(self.0.ctx.as_ptr(), self.0.as_js_value(), proto) {
177                Err(self.0.ctx.raise_exception())
178            } else {
179                Ok(())
180            }
181        }
182    }
183
184    /// Check instance of object
185    pub fn is_instance_of(&self, class: impl AsRef<Value<'js>>) -> bool {
186        let class = class.as_ref();
187        0 != unsafe {
188            qjs::JS_IsInstanceOf(
189                self.0.ctx.as_ptr(),
190                self.0.as_js_value(),
191                class.as_js_value(),
192            )
193        }
194    }
195
196    /// Convert into an array
197    pub fn into_array(self) -> Option<Array<'js>> {
198        if self.is_array() {
199            Some(Array(self))
200        } else {
201            None
202        }
203    }
204}
205
206/// The property filter
207#[derive(Debug, Clone, Copy)]
208#[repr(transparent)]
209pub struct Filter {
210    flags: qjs::c_int,
211}
212
213/// Include only enumerable string properties by default
214impl Default for Filter {
215    fn default() -> Self {
216        Self::new().string().enum_only()
217    }
218}
219
220impl Filter {
221    /// Create filter which includes nothing
222    pub fn new() -> Self {
223        Self { flags: 0 }
224    }
225
226    /// Include string properties
227    #[must_use]
228    pub fn string(mut self) -> Self {
229        self.flags |= qjs::JS_GPN_STRING_MASK as qjs::c_int;
230        self
231    }
232
233    /// Include symbol properties
234    #[must_use]
235    pub fn symbol(mut self) -> Self {
236        self.flags |= qjs::JS_GPN_SYMBOL_MASK as qjs::c_int;
237        self
238    }
239
240    /// Include private properties
241    #[must_use]
242    pub fn private(mut self) -> Self {
243        self.flags |= qjs::JS_GPN_PRIVATE_MASK as qjs::c_int;
244        self
245    }
246
247    /// Include only enumerable properties
248    #[must_use]
249    pub fn enum_only(mut self) -> Self {
250        self.flags |= qjs::JS_GPN_ENUM_ONLY as qjs::c_int;
251        self
252    }
253}
254
255struct IterState<'js> {
256    ctx: Ctx<'js>,
257    enums: *mut qjs::JSPropertyEnum,
258    index: u32,
259    count: u32,
260}
261
262impl<'js> IterState<'js> {
263    fn new(obj: &Value<'js>, flags: qjs::c_int) -> Result<Self> {
264        let ctx = obj.ctx();
265
266        let mut enums = mem::MaybeUninit::uninit();
267        let mut count = mem::MaybeUninit::uninit();
268
269        let (enums, count) = unsafe {
270            if qjs::JS_GetOwnPropertyNames(
271                ctx.as_ptr(),
272                enums.as_mut_ptr(),
273                count.as_mut_ptr(),
274                obj.value,
275                flags,
276            ) < 0
277            {
278                return Err(ctx.raise_exception());
279            }
280            let enums = enums.assume_init();
281            let count = count.assume_init();
282            (enums, count)
283        };
284
285        Ok(Self {
286            ctx: ctx.clone(),
287            enums,
288            count,
289            index: 0,
290        })
291    }
292}
293
294impl<'js> Drop for IterState<'js> {
295    fn drop(&mut self) {
296        // Free atoms which doesn't consumed by the iterator
297        for index in self.index..self.count {
298            let elem = unsafe { &*self.enums.offset(index as isize) };
299            unsafe { qjs::JS_FreeAtom(self.ctx.as_ptr(), elem.atom) };
300        }
301
302        // This is safe because iterator cannot outlive ctx
303        unsafe { qjs::js_free(self.ctx.as_ptr(), self.enums as _) };
304    }
305}
306
307impl<'js> Iterator for IterState<'js> {
308    type Item = Atom<'js>;
309
310    fn next(&mut self) -> Option<Self::Item> {
311        if self.index < self.count {
312            let elem = unsafe { &*self.enums.offset(self.index as _) };
313            self.index += 1;
314            let atom = unsafe { Atom::from_atom_val(self.ctx.clone(), elem.atom) };
315            Some(atom)
316        } else {
317            None
318        }
319    }
320
321    fn size_hint(&self) -> (usize, Option<usize>) {
322        let len = self.len();
323        (len, Some(len))
324    }
325}
326
327impl<'js> DoubleEndedIterator for IterState<'js> {
328    fn next_back(&mut self) -> Option<Self::Item> {
329        if self.index < self.count {
330            self.count -= 1;
331            let elem = unsafe { &*self.enums.offset(self.count as _) };
332            let atom = unsafe { Atom::from_atom_val(self.ctx.clone(), elem.atom) };
333            Some(atom)
334        } else {
335            None
336        }
337    }
338}
339
340impl<'js> ExactSizeIterator for IterState<'js> {
341    fn len(&self) -> usize {
342        (self.count - self.index) as _
343    }
344}
345
346impl<'js> FusedIterator for IterState<'js> {}
347
348/// The iterator for an object own keys
349pub struct ObjectKeysIter<'js, K> {
350    state: Option<Result<IterState<'js>>>,
351    marker: PhantomData<K>,
352}
353
354impl<'js, K> Iterator for ObjectKeysIter<'js, K>
355where
356    K: FromAtom<'js>,
357{
358    type Item = Result<K>;
359
360    fn next(&mut self) -> Option<Self::Item> {
361        if let Some(Ok(state)) = &mut self.state {
362            match state.next() {
363                Some(atom) => Some(K::from_atom(atom)),
364                None => {
365                    self.state = None;
366                    None
367                }
368            }
369        } else if self.state.is_none() {
370            None
371        } else if let Some(Err(error)) = self.state.take() {
372            Some(Err(error))
373        } else {
374            unreachable!();
375        }
376    }
377
378    fn size_hint(&self) -> (usize, Option<usize>) {
379        let len = self.len();
380        (len, Some(len))
381    }
382}
383
384impl<'js, K> DoubleEndedIterator for ObjectKeysIter<'js, K>
385where
386    K: FromAtom<'js>,
387{
388    fn next_back(&mut self) -> Option<Self::Item> {
389        if let Some(Ok(state)) = &mut self.state {
390            match state.next_back() {
391                Some(atom) => Some(K::from_atom(atom)),
392                None => {
393                    self.state = None;
394                    None
395                }
396            }
397        } else if self.state.is_none() {
398            None
399        } else if let Some(Err(error)) = self.state.take() {
400            Some(Err(error))
401        } else {
402            unreachable!();
403        }
404    }
405}
406
407impl<'js, K> ExactSizeIterator for ObjectKeysIter<'js, K>
408where
409    K: FromAtom<'js>,
410{
411    fn len(&self) -> usize {
412        if let Some(Ok(state)) = &self.state {
413            state.len()
414        } else {
415            0
416        }
417    }
418}
419
420impl<'js, K> FusedIterator for ObjectKeysIter<'js, K> where K: FromAtom<'js> {}
421
422/// The iterator for an object own properties
423pub struct ObjectIter<'js, K, V> {
424    state: Option<Result<IterState<'js>>>,
425    object: Object<'js>,
426    marker: PhantomData<(K, V)>,
427}
428
429impl<'js, K, V> Iterator for ObjectIter<'js, K, V>
430where
431    K: FromAtom<'js>,
432    V: FromJs<'js>,
433{
434    type Item = Result<(K, V)>;
435
436    fn next(&mut self) -> Option<Self::Item> {
437        if let Some(Ok(state)) = &mut self.state {
438            match state.next() {
439                Some(atom) => Some(
440                    K::from_atom(atom.clone())
441                        .and_then(|key| self.object.get(atom).map(|val| (key, val))),
442                ),
443                None => {
444                    self.state = None;
445                    None
446                }
447            }
448        } else if self.state.is_none() {
449            None
450        } else if let Some(Err(error)) = self.state.take() {
451            Some(Err(error))
452        } else {
453            unreachable!();
454        }
455    }
456
457    fn size_hint(&self) -> (usize, Option<usize>) {
458        let len = self.len();
459        (len, Some(len))
460    }
461}
462
463impl<'js, K, V> DoubleEndedIterator for ObjectIter<'js, K, V>
464where
465    K: FromAtom<'js>,
466    V: FromJs<'js>,
467{
468    fn next_back(&mut self) -> Option<Self::Item> {
469        if let Some(Ok(state)) = &mut self.state {
470            match state.next_back() {
471                Some(atom) => Some(
472                    K::from_atom(atom.clone())
473                        .and_then(|key| self.object.get(atom).map(|val| (key, val))),
474                ),
475                None => {
476                    self.state = None;
477                    None
478                }
479            }
480        } else if self.state.is_none() {
481            None
482        } else if let Some(Err(error)) = self.state.take() {
483            Some(Err(error))
484        } else {
485            unreachable!();
486        }
487    }
488}
489
490impl<'js, K, V> ExactSizeIterator for ObjectIter<'js, K, V>
491where
492    K: FromAtom<'js>,
493    V: FromJs<'js>,
494{
495    fn len(&self) -> usize {
496        if let Some(Ok(state)) = &self.state {
497            state.len()
498        } else {
499            0
500        }
501    }
502}
503
504impl<'js, K, V> FusedIterator for ObjectIter<'js, K, V>
505where
506    K: FromAtom<'js>,
507    V: FromJs<'js>,
508{
509}
510
511/// The iterator for an object own property values
512pub struct ObjectValuesIter<'js, V> {
513    state: Option<Result<IterState<'js>>>,
514    object: Object<'js>,
515    marker: PhantomData<V>,
516}
517
518impl<'js, V> Iterator for ObjectValuesIter<'js, V>
519where
520    V: FromJs<'js>,
521{
522    type Item = Result<V>;
523
524    fn next(&mut self) -> Option<Self::Item> {
525        if let Some(Ok(state)) = &mut self.state {
526            match state.next() {
527                Some(atom) => Some(self.object.get(atom)),
528                None => {
529                    self.state = None;
530                    None
531                }
532            }
533        } else if self.state.is_none() {
534            None
535        } else if let Some(Err(error)) = self.state.take() {
536            Some(Err(error))
537        } else {
538            unreachable!();
539        }
540    }
541
542    fn size_hint(&self) -> (usize, Option<usize>) {
543        let len = self.len();
544        (len, Some(len))
545    }
546}
547
548impl<'js, V> DoubleEndedIterator for ObjectValuesIter<'js, V>
549where
550    V: FromJs<'js>,
551{
552    fn next_back(&mut self) -> Option<Self::Item> {
553        if let Some(Ok(state)) = &mut self.state {
554            match state.next_back() {
555                Some(atom) => Some(self.object.get(atom)),
556                None => {
557                    self.state = None;
558                    None
559                }
560            }
561        } else if self.state.is_none() {
562            None
563        } else if let Some(Err(error)) = self.state.take() {
564            Some(Err(error))
565        } else {
566            unreachable!();
567        }
568    }
569}
570
571impl<'js, V> ExactSizeIterator for ObjectValuesIter<'js, V>
572where
573    V: FromJs<'js>,
574{
575    fn len(&self) -> usize {
576        if let Some(Ok(state)) = &self.state {
577            state.len()
578        } else {
579            0
580        }
581    }
582}
583
584impl<'js, V> FusedIterator for ObjectValuesIter<'js, V> where V: FromJs<'js> {}
585
586impl<'js> IntoIterator for Object<'js> {
587    type Item = Result<(Atom<'js>, Value<'js>)>;
588    type IntoIter = ObjectIter<'js, Atom<'js>, Value<'js>>;
589
590    fn into_iter(self) -> Self::IntoIter {
591        let flags = qjs::JS_GPN_STRING_MASK as _;
592        ObjectIter {
593            state: Some(IterState::new(&self.0, flags)),
594            object: self,
595            marker: PhantomData,
596        }
597    }
598}
599
600impl<'js, K, V> FromIteratorJs<'js, (K, V)> for Object<'js>
601where
602    K: IntoAtom<'js>,
603    V: IntoJs<'js>,
604{
605    type Item = (Atom<'js>, Value<'js>);
606
607    fn from_iter_js<T>(ctx: &Ctx<'js>, iter: T) -> Result<Self>
608    where
609        T: IntoIterator<Item = (K, V)>,
610    {
611        let object = Object::new(ctx.clone())?;
612        for (key, value) in iter {
613            let key = key.into_atom(ctx)?;
614            let value = value.into_js(ctx)?;
615            object.set(key, value)?;
616        }
617        Ok(object)
618    }
619}
620
621#[cfg(test)]
622mod test {
623    use crate::*;
624
625    #[test]
626    fn from_javascript() {
627        test_with(|ctx| {
628            let val: Object = ctx
629                .eval(
630                    r#"
631                let obj = {};
632                obj['a'] = 3;
633                obj[3] = 'a';
634                obj
635            "#,
636                )
637                .unwrap();
638
639            let text: StdString = val.get(3).unwrap();
640            assert_eq!(text, "a");
641            let int: i32 = val.get("a").unwrap();
642            assert_eq!(int, 3);
643            let int: StdString = val.get(3).unwrap();
644            assert_eq!(int, "a");
645            val.set("hallo", "foo").unwrap();
646            let text: StdString = val.get("hallo").unwrap();
647            assert_eq!(text, "foo".to_string());
648            val.remove("hallo").unwrap();
649            let text: Option<StdString> = val.get("hallo").unwrap();
650            assert_eq!(text, None);
651        });
652    }
653
654    #[test]
655    fn types() {
656        test_with(|ctx| {
657            let val: Object = ctx
658                .eval(
659                    r#"
660                let array_3 = [];
661                array_3[3] = "foo";
662                array_3[99] = 4;
663                ({
664                    array_1: [0,1,2,3,4,5],
665                    array_2: [0,"foo",{},undefined,4,5],
666                    array_3: array_3,
667                    func_1: () => 1,
668                    func_2: function(){ return "foo"},
669                    obj_1: {
670                        a: 1,
671                        b: "foo",
672                    },
673                })
674                "#,
675                )
676                .unwrap();
677            assert!(val.get::<_, Object>("array_1").unwrap().is_array());
678            assert!(val.get::<_, Object>("array_2").unwrap().is_array());
679            assert!(val.get::<_, Object>("array_3").unwrap().is_array());
680            assert!(val.get::<_, Object>("func_1").unwrap().is_function());
681            assert!(val.get::<_, Object>("func_2").unwrap().is_function());
682            assert!(!val.get::<_, Object>("obj_1").unwrap().is_function());
683            assert!(!val.get::<_, Object>("obj_1").unwrap().is_array());
684        })
685    }
686
687    #[test]
688    fn own_keys_iter() {
689        test_with(|ctx| {
690            let val: Object = ctx
691                .eval(
692                    r#"
693                   ({
694                     123: 123,
695                     str: "abc",
696                     arr: [],
697                     '': undefined,
698                   })
699                "#,
700                )
701                .unwrap();
702            let keys = val.keys().collect::<Result<Vec<StdString>>>().unwrap();
703            assert_eq!(keys.len(), 4);
704            assert_eq!(keys[0], "123");
705            assert_eq!(keys[1], "str");
706            assert_eq!(keys[2], "arr");
707            assert_eq!(keys[3], "");
708        })
709    }
710
711    #[test]
712    fn own_props_iter() {
713        test_with(|ctx| {
714            let val: Object = ctx
715                .eval(
716                    r#"
717                   ({
718                     123: "",
719                     str: "abc",
720                     '': "def",
721                   })
722                "#,
723                )
724                .unwrap();
725            let pairs = val
726                .props()
727                .collect::<Result<Vec<(StdString, StdString)>>>()
728                .unwrap();
729            assert_eq!(pairs.len(), 3);
730            assert_eq!(pairs[0].0, "123");
731            assert_eq!(pairs[0].1, "");
732            assert_eq!(pairs[1].0, "str");
733            assert_eq!(pairs[1].1, "abc");
734            assert_eq!(pairs[2].0, "");
735            assert_eq!(pairs[2].1, "def");
736        })
737    }
738
739    #[test]
740    fn into_iter() {
741        test_with(|ctx| {
742            let val: Object = ctx
743                .eval(
744                    r#"
745                   ({
746                     123: 123,
747                     str: "abc",
748                     arr: [],
749                     '': undefined,
750                   })
751                "#,
752                )
753                .unwrap();
754            let pairs = val.into_iter().collect::<Result<Vec<_>>>().unwrap();
755            assert_eq!(pairs.len(), 4);
756            assert_eq!(pairs[0].0.clone().to_string().unwrap(), "123");
757            assert_eq!(i32::from_js(&ctx, pairs[0].1.clone()).unwrap(), 123);
758            assert_eq!(pairs[1].0.clone().to_string().unwrap(), "str");
759            assert_eq!(StdString::from_js(&ctx, pairs[1].1.clone()).unwrap(), "abc");
760            assert_eq!(pairs[2].0.clone().to_string().unwrap(), "arr");
761            assert_eq!(Array::from_js(&ctx, pairs[2].1.clone()).unwrap().len(), 0);
762            assert_eq!(pairs[3].0.clone().to_string().unwrap(), "");
763            assert_eq!(
764                Undefined::from_js(&ctx, pairs[3].1.clone()).unwrap(),
765                Undefined
766            );
767        })
768    }
769
770    #[test]
771    fn iter_take() {
772        test_with(|ctx| {
773            let val: Object = ctx
774                .eval(
775                    r#"
776                   ({
777                     123: 123,
778                     str: "abc",
779                     arr: [],
780                     '': undefined,
781                   })
782                "#,
783                )
784                .unwrap();
785            let keys = val
786                .keys()
787                .take(1)
788                .collect::<Result<Vec<StdString>>>()
789                .unwrap();
790            assert_eq!(keys.len(), 1);
791            assert_eq!(keys[0], "123");
792        })
793    }
794
795    #[test]
796    fn new_proto_with_empty_prototype() {
797        test_with(|ctx| {
798            // Object.create({}) equivalent — prototype should be an empty object
799            let proto = Object::new(ctx.clone()).unwrap();
800            let obj = Object::new_proto(ctx.clone(), Some(&proto)).unwrap();
801            obj.set("a", 1).unwrap();
802
803            let got_proto = obj.get_prototype().expect("should have a prototype");
804            // The prototype itself should have no own enumerable properties
805            assert_eq!(got_proto.keys::<StdString>().count(), 0);
806            // But the object's own properties should work
807            let v: i32 = obj.get("a").unwrap();
808            assert_eq!(v, 1);
809        })
810    }
811
812    #[test]
813    fn new_proto_with_null_prototype() {
814        test_with(|ctx| {
815            // Object.create(null) equivalent
816            let obj = Object::new_proto(ctx.clone(), None).unwrap();
817            obj.set("x", 42).unwrap();
818
819            assert!(obj.get_prototype().is_none());
820            let v: i32 = obj.get("x").unwrap();
821            assert_eq!(v, 42);
822        })
823    }
824
825    #[test]
826    fn new_proto_console_namespace() {
827        // Reproduces the exact scenario from issue #572
828        test_with(|ctx| {
829            let proto = Object::new(ctx.clone()).unwrap();
830            let console = Object::new_proto(ctx.clone(), Some(&proto)).unwrap();
831            ctx.globals().set("console", console).unwrap();
832
833            let names: Array = ctx
834                .eval("Object.getOwnPropertyNames(Object.getPrototypeOf(console))")
835                .unwrap();
836            assert_eq!(names.len(), 0);
837        })
838    }
839
840    #[test]
841    fn collect_js() {
842        test_with(|ctx| {
843            let object = [("a", "bc"), ("$_", ""), ("", "xyz")]
844                .iter()
845                .cloned()
846                .collect_js::<Object>(&ctx)
847                .unwrap();
848            assert_eq!(
849                StdString::from_js(&ctx, object.get("a").unwrap()).unwrap(),
850                "bc"
851            );
852            assert_eq!(
853                StdString::from_js(&ctx, object.get("$_").unwrap()).unwrap(),
854                ""
855            );
856            assert_eq!(
857                StdString::from_js(&ctx, object.get("").unwrap()).unwrap(),
858                "xyz"
859            );
860        })
861    }
862}