1use std::collections::HashMap;
8use std::iter::FusedIterator;
9use std::mem::MaybeUninit;
10use std::ops::{Deref, DerefMut};
11use std::slice;
12
13use mozjs::jsapi::{
14 CurrentGlobalOrNull, ESClass, GetBuiltinClass, GetPropertyKeys, JS_DefineFunctionById, JS_DefineFunctions,
15 JS_DefineFunctionsWithHelp, JS_DefineProperties, JS_DefinePropertyById2, JS_DeletePropertyById, JS_GetPropertyById,
16 JS_GetPropertyDescriptorById, JS_HasOwnPropertyById, JS_HasPropertyById, JS_NewPlainObject, JS_SetPropertyById,
17 JSFunctionSpec, JSFunctionSpecWithHelp, JSObject, JSPropertySpec, PropertyKey as JSPropertyKey, Unbox,
18};
19use mozjs::jsval::NullValue;
20use mozjs::rust::IdVector;
21
22use crate::conversions::{FromValue, ToPropertyKey, ToValue};
23use crate::flags::{IteratorFlags, PropertyFlags};
24use crate::function::NativeFunction;
25use crate::{Context, Error, Exception, Function, Local, OwnedKey, PropertyDescriptor, PropertyKey, Result, Value};
26
27#[derive(Debug)]
31pub struct Object<'o> {
32 obj: Local<'o, *mut JSObject>,
33}
34
35impl<'o> Object<'o> {
36 pub fn new(cx: &'o Context) -> Object<'o> {
38 Object::from(cx.root(unsafe { JS_NewPlainObject(cx.as_ptr()) }))
39 }
40
41 pub fn null(cx: &'o Context) -> Object<'o> {
45 Object::from(cx.root(NullValue().to_object_or_null()))
46 }
47
48 pub fn global(cx: &'o Context) -> Object<'o> {
50 Object::from(cx.root(unsafe { CurrentGlobalOrNull(cx.as_ptr()) }))
51 }
52
53 pub fn has<'cx, K: ToPropertyKey<'cx>>(&self, cx: &'cx Context, key: K) -> bool {
55 let key = key.to_key(cx).unwrap();
56 let mut found = false;
57 if unsafe { JS_HasPropertyById(cx.as_ptr(), self.handle().into(), key.handle().into(), &raw mut found) } {
58 found
59 } else {
60 Exception::clear(cx);
61 false
62 }
63 }
64
65 pub fn has_own<'cx, K: ToPropertyKey<'cx>>(&self, cx: &'cx Context, key: K) -> bool {
69 let key = key.to_key(cx).unwrap();
70 let mut found = false;
71 if unsafe { JS_HasOwnPropertyById(cx.as_ptr(), self.handle().into(), key.handle().into(), &raw mut found) } {
72 found
73 } else {
74 Exception::clear(cx);
75 false
76 }
77 }
78
79 pub fn get<'cx, K: ToPropertyKey<'cx>>(&self, cx: &'cx Context, key: K) -> Result<Option<Value<'cx>>> {
83 let key = key.to_key(cx).unwrap();
84 if self.has(cx, &key) {
85 let mut rval = Value::undefined(cx);
86 let res = unsafe {
87 JS_GetPropertyById(
88 cx.as_ptr(),
89 self.handle().into(),
90 key.handle().into(),
91 rval.handle_mut().into(),
92 )
93 };
94
95 if res { Ok(Some(rval)) } else { Err(Error::none()) }
96 } else {
97 Ok(None)
98 }
99 }
100
101 pub fn get_as<'cx, K: ToPropertyKey<'cx>, T: FromValue<'cx>>(
104 &self, cx: &'cx Context, key: K, strict: bool, config: T::Config,
105 ) -> Result<Option<T>> {
106 self.get(cx, key)?.map(|val| T::from_value(cx, &val, strict, config)).transpose()
107 }
108
109 pub fn get_descriptor<'cx, K: ToPropertyKey<'cx>>(
112 &self, cx: &'cx Context, key: K,
113 ) -> Result<Option<PropertyDescriptor<'cx>>> {
114 let key = key.to_key(cx).unwrap();
115 if self.has(cx, &key) {
116 let mut desc = PropertyDescriptor::empty(cx);
117 let mut holder = Object::null(cx);
118 let mut is_none = true;
119 let res = unsafe {
120 JS_GetPropertyDescriptorById(
121 cx.as_ptr(),
122 self.handle().into(),
123 key.handle().into(),
124 desc.handle_mut().into(),
125 holder.handle_mut().into(),
126 &raw mut is_none,
127 )
128 };
129
130 if !res {
131 Err(Error::none())
132 } else if is_none {
133 Ok(None)
134 } else {
135 Ok(Some(desc))
136 }
137 } else {
138 Ok(None)
139 }
140 }
141
142 pub fn set<'cx, K: ToPropertyKey<'cx>>(&self, cx: &'cx Context, key: K, value: &Value) -> bool {
146 let key = key.to_key(cx).unwrap();
147 unsafe {
148 JS_SetPropertyById(
149 cx.as_ptr(),
150 self.handle().into(),
151 key.handle().into(),
152 value.handle().into(),
153 )
154 }
155 }
156
157 pub fn set_as<'cx, K: ToPropertyKey<'cx>, T: ToValue<'cx> + ?Sized>(
161 &self, cx: &'cx Context, key: K, value: &T,
162 ) -> bool {
163 self.set(cx, key, &value.as_value(cx))
164 }
165
166 pub fn define<'cx, K: ToPropertyKey<'cx>>(
170 &self, cx: &'cx Context, key: K, value: &Value, attrs: PropertyFlags,
171 ) -> bool {
172 let key = key.to_key(cx).unwrap();
173 unsafe {
174 JS_DefinePropertyById2(
175 cx.as_ptr(),
176 self.handle().into(),
177 key.handle().into(),
178 value.handle().into(),
179 u32::from(attrs.bits()),
180 )
181 }
182 }
183
184 pub fn define_as<'cx, K: ToPropertyKey<'cx>, T: ToValue<'cx> + ?Sized>(
188 &self, cx: &'cx Context, key: K, value: &T, attrs: PropertyFlags,
189 ) -> bool {
190 self.define(cx, key, &value.as_value(cx), attrs)
191 }
192
193 pub fn define_method<'cx, K: ToPropertyKey<'cx>>(
197 &self, cx: &'cx Context, key: K, method: NativeFunction, nargs: u32, attrs: PropertyFlags,
198 ) -> Function<'cx> {
199 let key = key.to_key(cx).unwrap();
200 cx.root(unsafe {
201 JS_DefineFunctionById(
202 cx.as_ptr(),
203 self.handle().into(),
204 key.handle().into(),
205 Some(method),
206 nargs,
207 u32::from(attrs.bits()),
208 )
209 })
210 .into()
211 }
212
213 #[cfg_attr(
217 feature = "macros",
218 doc = "\nThey can be created through [function_spec](crate::function_spec)."
219 )]
220 pub unsafe fn define_methods(&self, cx: &Context, methods: &[JSFunctionSpec]) -> bool {
221 unsafe { JS_DefineFunctions(cx.as_ptr(), self.handle().into(), methods.as_ptr()) }
222 }
223
224 pub unsafe fn define_methods_with_help(&self, cx: &Context, methods: &[JSFunctionSpecWithHelp]) -> bool {
228 unsafe { JS_DefineFunctionsWithHelp(cx.as_ptr(), self.handle().into(), methods.as_ptr()) }
229 }
230
231 pub unsafe fn define_properties(&self, cx: &Context, properties: &[JSPropertySpec]) -> bool {
235 unsafe { JS_DefineProperties(cx.as_ptr(), self.handle().into(), properties.as_ptr()) }
236 }
237
238 pub fn delete<'cx, K: ToPropertyKey<'cx>>(&self, cx: &'cx Context, key: K) -> bool {
242 let key = key.to_key(cx).unwrap();
243 let mut result = MaybeUninit::uninit();
244 unsafe {
245 JS_DeletePropertyById(
246 cx.as_ptr(),
247 self.handle().into(),
248 key.handle().into(),
249 result.as_mut_ptr(),
250 )
251 }
252 }
253
254 pub fn get_builtin_class(&self, cx: &Context) -> ESClass {
258 let mut class = ESClass::Other;
259 unsafe {
260 GetBuiltinClass(cx.as_ptr(), self.handle().into(), &raw mut class);
261 }
262 class
263 }
264
265 pub fn is_boxed_primitive(&self, cx: &Context) -> Option<ESClass> {
269 let class = self.get_builtin_class(cx);
270 match class {
271 ESClass::Boolean | ESClass::Number | ESClass::String | ESClass::BigInt => Some(class),
272 _ => None,
273 }
274 }
275
276 pub fn unbox_primitive<'cx>(&self, cx: &'cx Context) -> Option<Value<'cx>> {
278 if self.is_boxed_primitive(cx).is_some() {
279 let mut rval = Value::undefined(cx);
280 if unsafe { Unbox(cx.as_ptr(), self.handle().into(), rval.handle_mut().into()) } {
281 return Some(rval);
282 }
283 }
284 None
285 }
286
287 pub fn keys<'cx>(&self, cx: &'cx Context, flags: Option<IteratorFlags>) -> ObjectKeysIter<'cx> {
290 let flags = flags.unwrap_or(IteratorFlags::OWN_ONLY);
291 let mut ids = unsafe { IdVector::new(cx.as_ptr()) };
292 unsafe { GetPropertyKeys(cx.as_ptr(), self.handle().into(), flags.bits(), ids.handle_mut()) };
293 ObjectKeysIter::new(cx, ids)
294 }
295
296 pub fn iter<'cx, 's>(&'s self, cx: &'cx Context, flags: Option<IteratorFlags>) -> ObjectIter<'cx, 's>
297 where
298 'o: 'cx,
299 {
300 ObjectIter::new(self, self.keys(cx, flags))
301 }
302
303 pub fn to_hashmap<'cx>(
304 &self, cx: &'cx Context, flags: Option<IteratorFlags>,
305 ) -> Result<HashMap<OwnedKey<'cx>, Value<'cx>>>
306 where
307 'o: 'cx,
308 {
309 self.iter(cx, flags).map(|(k, v)| Ok((k.to_owned_key(cx)?, v?))).collect()
310 }
311
312 pub fn into_local(self) -> Local<'o, *mut JSObject> {
313 self.obj
314 }
315}
316
317impl<'o> From<Local<'o, *mut JSObject>> for Object<'o> {
318 fn from(obj: Local<'o, *mut JSObject>) -> Object<'o> {
319 Object { obj }
320 }
321}
322
323impl<'o> Deref for Object<'o> {
324 type Target = Local<'o, *mut JSObject>;
325
326 fn deref(&self) -> &Self::Target {
327 &self.obj
328 }
329}
330
331impl DerefMut for Object<'_> {
332 fn deref_mut(&mut self) -> &mut Self::Target {
333 &mut self.obj
334 }
335}
336
337pub struct ObjectKeysIter<'cx> {
338 cx: &'cx Context,
339 slice: &'static [JSPropertyKey],
340 keys: IdVector,
341 index: usize,
342 count: usize,
343}
344
345impl<'cx> ObjectKeysIter<'cx> {
346 fn new(cx: &'cx Context, keys: IdVector) -> ObjectKeysIter<'cx> {
347 let keys_slice = &*keys;
348 let count = keys_slice.len();
349 let keys_slice = unsafe { slice::from_raw_parts(keys_slice.as_ptr(), count) };
350 ObjectKeysIter {
351 cx,
352 slice: keys_slice,
353 keys,
354 index: 0,
355 count,
356 }
357 }
358
359 pub fn into_owned(self) -> ObjectOwnedKeysIter<'cx> {
360 ObjectOwnedKeysIter { iter: self }
361 }
362}
363
364impl<'cx> Iterator for ObjectKeysIter<'cx> {
365 type Item = PropertyKey<'cx>;
366
367 fn next(&mut self) -> Option<Self::Item> {
368 (self.index < self.count).then(|| {
369 let key = &self.slice[self.index];
370 self.index += 1;
371 self.cx.root(*key).into()
372 })
373 }
374
375 fn size_hint(&self) -> (usize, Option<usize>) {
376 (self.count - self.index, Some(self.count - self.index))
377 }
378}
379
380impl DoubleEndedIterator for ObjectKeysIter<'_> {
381 fn next_back(&mut self) -> Option<Self::Item> {
382 (self.index < self.count).then(|| {
383 self.count -= 1;
384 let key = &self.keys[self.count];
385 self.cx.root(*key).into()
386 })
387 }
388}
389
390impl ExactSizeIterator for ObjectKeysIter<'_> {
391 fn len(&self) -> usize {
392 self.count - self.index
393 }
394}
395
396impl FusedIterator for ObjectKeysIter<'_> {}
397
398pub struct ObjectOwnedKeysIter<'cx> {
399 iter: ObjectKeysIter<'cx>,
400}
401
402impl<'cx> Iterator for ObjectOwnedKeysIter<'cx> {
403 type Item = Result<OwnedKey<'cx>>;
404
405 fn next(&mut self) -> Option<Result<OwnedKey<'cx>>> {
406 self.iter.next().map(|key| key.to_owned_key(self.iter.cx))
407 }
408
409 fn size_hint(&self) -> (usize, Option<usize>) {
410 self.iter.size_hint()
411 }
412}
413
414impl DoubleEndedIterator for ObjectOwnedKeysIter<'_> {
415 fn next_back(&mut self) -> Option<Self::Item> {
416 self.iter.next_back().map(|key| key.to_owned_key(self.iter.cx))
417 }
418}
419
420impl ExactSizeIterator for ObjectOwnedKeysIter<'_> {
421 fn len(&self) -> usize {
422 self.iter.len()
423 }
424}
425
426impl FusedIterator for ObjectOwnedKeysIter<'_> {}
427
428pub struct ObjectIter<'cx, 'o> {
429 object: &'o Object<'cx>,
430 keys: ObjectKeysIter<'cx>,
431}
432
433impl<'cx, 'o> ObjectIter<'cx, 'o> {
434 fn new(object: &'o Object<'cx>, keys: ObjectKeysIter<'cx>) -> ObjectIter<'cx, 'o> {
435 ObjectIter { object, keys }
436 }
437}
438
439impl<'cx> Iterator for ObjectIter<'cx, '_> {
440 type Item = (PropertyKey<'cx>, Result<Value<'cx>>);
441
442 fn next(&mut self) -> Option<Self::Item> {
443 self.keys.next().map(|key| {
444 let value = self.object.get(self.keys.cx, &key).transpose().unwrap();
445 (key, value)
446 })
447 }
448
449 fn size_hint(&self) -> (usize, Option<usize>) {
450 self.keys.size_hint()
451 }
452}
453
454impl DoubleEndedIterator for ObjectIter<'_, '_> {
455 fn next_back(&mut self) -> Option<Self::Item> {
456 self.keys.next_back().map(|key| {
457 let value = self.object.get(self.keys.cx, &key).transpose().unwrap();
458 (key, value)
459 })
460 }
461}
462
463impl ExactSizeIterator for ObjectIter<'_, '_> {
464 fn len(&self) -> usize {
465 self.keys.len()
466 }
467}
468
469impl FusedIterator for ObjectIter<'_, '_> {}
470
471#[cfg(test)]
472mod tests {
473 use crate::conversions::FromValue as _;
474 use crate::flags::{IteratorFlags, PropertyFlags};
475 use crate::symbol::WellKnownSymbolCode;
476 use crate::utils::test::TestRuntime;
477 use crate::{Context, Object, OwnedKey, Symbol, Value};
478
479 type Property = (&'static str, i32);
480
481 const SET: Property = ("set_key", 0);
482 const DEFINE: Property = ("def_key", 1);
483 const ENUMERABLE: Property = ("enum_key", 2);
484
485 const ENUMERABLE_PROPERTIES: [Property; 2] = [SET, ENUMERABLE];
486 const PROPERTIES: [Property; 3] = [SET, DEFINE, ENUMERABLE];
487
488 fn create_object(cx: &Context) -> Object<'_> {
489 let object = Object::new(cx);
490
491 assert!(object.set(cx, SET.0, &Value::i32(cx, SET.1)));
492 assert!(object.define(cx, DEFINE.0, &Value::i32(cx, DEFINE.1), PropertyFlags::CONSTANT));
493 assert!(object.define(cx, ENUMERABLE.0, &Value::i32(cx, ENUMERABLE.1), PropertyFlags::all()));
494
495 object
496 }
497
498 fn create_mixed_object(cx: &Context) -> Object<'_> {
499 let object = Object::new(cx);
500
501 assert!(object.set(cx, 16, &Value::i32(cx, 16)));
502 assert!(object.set(cx, SET.0, &Value::i32(cx, SET.1)));
503 assert!(object.set(cx, WellKnownSymbolCode::ToStringTag, &Value::string(cx, "Mixed")));
504
505 object
506 }
507
508 #[test]
509 fn global() {
510 let rt = TestRuntime::new();
511 let cx = &rt.cx;
512
513 let global = Object::global(cx);
514 assert_eq!(rt.global, global.handle().get());
515 }
516
517 #[test]
518 fn property() {
519 let rt = TestRuntime::new();
520 let cx = &rt.cx;
521
522 let object = create_object(cx);
523
524 assert!(object.has(cx, SET.0));
525 assert!(object.has_own(cx, DEFINE.0));
526
527 let value = object.get(cx, SET.0).unwrap().unwrap();
528 assert_eq!(SET.1, value.handle().to_int32());
529
530 let descriptor = object.get_descriptor(cx, SET.0).unwrap().unwrap();
531 assert!(descriptor.is_configurable());
532 assert!(descriptor.is_enumerable());
533 assert!(descriptor.is_writable());
534 assert!(!descriptor.is_resolving());
535 assert_eq!(SET.1, descriptor.value(cx).unwrap().handle().to_int32());
536
537 let value = object.get(cx, DEFINE.0).unwrap().unwrap();
538 assert_eq!(DEFINE.1, value.handle().to_int32());
539
540 let descriptor = object.get_descriptor(cx, DEFINE.0).unwrap().unwrap();
541 assert!(!descriptor.is_configurable());
542 assert!(!descriptor.is_enumerable());
543 assert!(!descriptor.is_writable());
544 assert!(!descriptor.is_resolving());
545 assert_eq!(DEFINE.1, descriptor.value(cx).unwrap().handle().to_int32());
546
547 assert!(object.delete(cx, SET.0));
548 assert!(object.delete(cx, DEFINE.0));
549 assert!(object.get(cx, SET.0).unwrap().is_none());
550 assert!(object.get(cx, DEFINE.0).unwrap().is_some());
551 }
552
553 #[test]
554 fn iter() {
555 let rt = TestRuntime::new();
556 let cx = &rt.cx;
557
558 let object = create_object(cx);
559
560 let properties = [
561 (ENUMERABLE_PROPERTIES.as_slice(), None),
562 (
563 PROPERTIES.as_slice(),
564 Some(IteratorFlags::OWN_ONLY | IteratorFlags::HIDDEN),
565 ),
566 ];
567
568 for (properties, flags) in properties {
569 for (i, key) in object.keys(cx, flags).into_owned().enumerate() {
570 assert_eq!(OwnedKey::String(String::from(properties[i].0)), key.unwrap());
571 }
572
573 for (i, (key, value)) in object.iter(cx, flags).enumerate() {
574 assert_eq!(
575 OwnedKey::String(String::from(properties[i].0)),
576 key.to_owned_key(cx).unwrap()
577 );
578 assert_eq!(properties[i].1, value.unwrap().handle().to_int32());
579 }
580 }
581 }
582
583 #[test]
584 fn mixed_iter() {
585 let rt = TestRuntime::new();
586 let cx = &rt.cx;
587
588 let object = create_mixed_object(cx);
589 let flags = Some(IteratorFlags::SYMBOLS);
590
591 let mut keys = object.keys(cx, flags).into_owned();
592 assert_eq!(OwnedKey::Int(16), keys.next().unwrap().unwrap());
593 assert_eq!(OwnedKey::String(String::from(SET.0)), keys.next().unwrap().unwrap());
594 assert_eq!(
595 OwnedKey::Symbol(Symbol::well_known(cx, WellKnownSymbolCode::ToStringTag)),
596 keys.next().unwrap().unwrap()
597 );
598
599 let mut values = object.iter(cx, flags);
600 assert_eq!(16, values.next().unwrap().1.unwrap().handle().to_int32());
601 assert_eq!(SET.1, values.next().unwrap().1.unwrap().handle().to_int32());
602 assert_eq!(
603 "Mixed",
604 crate::String::from_value(cx, &values.next().unwrap().1.unwrap(), true, ())
605 .unwrap()
606 .to_owned(cx)
607 .unwrap()
608 );
609 }
610}