ion/object/
object.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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/// Represents an [Object] in the JS Runtime.
28///
29/// Refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) for more details.
30#[derive(Debug)]
31pub struct Object<'o> {
32	obj: Local<'o, *mut JSObject>,
33}
34
35impl<'o> Object<'o> {
36	/// Creates a plain empty [Object].
37	pub fn new(cx: &'o Context) -> Object<'o> {
38		Object::from(cx.root(unsafe { JS_NewPlainObject(cx.as_ptr()) }))
39	}
40
41	/// Creates a `null` "Object".
42	///
43	/// Most operations on this will result in an error, so be wary of where it is used.
44	pub fn null(cx: &'o Context) -> Object<'o> {
45		Object::from(cx.root(NullValue().to_object_or_null()))
46	}
47
48	/// Returns the current global object or `null` if one has not been initialised yet.
49	pub fn global(cx: &'o Context) -> Object<'o> {
50		Object::from(cx.root(unsafe { CurrentGlobalOrNull(cx.as_ptr()) }))
51	}
52
53	/// Checks if the [Object] has a value at the given key.
54	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	/// Checks if the [Object] has its own value at the given key.
66	///
67	/// An object owns its properties if they are not inherited from a prototype.
68	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	/// Gets the [Value] at the given key of the [Object].
80	///
81	/// Returns [None] if there is no value at the given key.
82	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	/// Gets the value at the given key of the [Object]. as a Rust type.
102	/// Returns [None] if the object does not contain the key or conversion to the Rust type fails.
103	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	/// Gets the descriptor at the given key of the [Object].
110	/// Returns [None] if the object does not contain the key.
111	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	/// Sets the [Value] at the given key of the [Object].
143	///
144	/// Returns `false` if the property cannot be set.
145	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	/// Sets the Rust type at the given key of the [Object].
158	///
159	/// Returns `false` if the property cannot be set.
160	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	/// Defines the [Value] at the given key of the [Object] with the given attributes.
167	///
168	/// Returns `false` if the property cannot be defined.
169	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	/// Defines the Rust type at the given key of the [Object] with the given attributes.
185	///
186	/// Returns `false` if the property cannot be defined.
187	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	/// Defines a method with the given name, and the given number of arguments and attributes on the [Object].
194	///
195	/// Parameters are similar to [create_function_spec](crate::spec::create_function_spec).
196	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	/// Defines methods on the objects using the given [specs](JSFunctionSpec).
214	///
215	/// The final element of the `methods` slice must be `JSFunctionSpec::ZERO`.
216	#[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	/// Defines methods on the objects using the given [specs](JSFunctionSpecWithHelp), with help.
225	///
226	/// The final element of the `methods` slice must be `JSFunctionSpecWithHelp::ZERO`.
227	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	/// Defines properties on the object using the given [specs](JSPropertySpec).
232	///
233	/// The final element of the `properties` slice must be `JSPropertySpec::ZERO`.
234	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	/// Deletes the [Value] at the given index.
239	///
240	/// Returns `false` if the element cannot be deleted.
241	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	/// Gets the builtin class of the object as described in the ECMAScript specification.
255	///
256	/// Returns [ESClass::Other] for other projects or proxies that cannot be unwrapped.
257	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	/// Returns the builtin class of the object if it a wrapper around a primitive.
266	///
267	/// The boxed types are `Boolean`, `Number`, `String` and `BigInt`
268	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	/// Unboxes primitive wrappers. See [Object::is_boxed_primitive] for details.
277	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	/// Returns an iterator of the keys of the [Object].
288	/// Each key can be a [String], [Symbol](crate::symbol) or integer.
289	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}