1use 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#[derive(Debug, PartialEq, Clone, Hash, Eq)]
14#[repr(transparent)]
15pub struct Object<'js>(pub(crate) Value<'js>);
16
17impl<'js> Object<'js> {
18 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 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 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 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 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 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 pub fn is_empty(&self) -> bool {
102 self.keys::<Atom>().next().is_none()
103 }
104
105 pub fn len(&self) -> usize {
107 self.keys::<Atom>().count()
108 }
109
110 pub fn keys<K: FromAtom<'js>>(&self) -> ObjectKeysIter<'js, K> {
112 self.own_keys(Filter::default())
113 }
114
115 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 pub fn props<K: FromAtom<'js>, V: FromJs<'js>>(&self) -> ObjectIter<'js, K, V> {
125 self.own_props(Filter::default())
126 }
127
128 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 pub fn values<K: FromAtom<'js>>(&self) -> ObjectValuesIter<'js, K> {
142 self.own_values(Filter::default())
143 }
144
145 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 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 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 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 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#[derive(Debug, Clone, Copy)]
208#[repr(transparent)]
209pub struct Filter {
210 flags: qjs::c_int,
211}
212
213impl Default for Filter {
215 fn default() -> Self {
216 Self::new().string().enum_only()
217 }
218}
219
220impl Filter {
221 pub fn new() -> Self {
223 Self { flags: 0 }
224 }
225
226 #[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 #[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 #[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 #[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 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 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
348pub 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
422pub 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
511pub 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 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 assert_eq!(got_proto.keys::<StdString>().count(), 0);
806 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 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 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}