boa/builtins/object/
mod.rs

1//! This module implements the global `Object` object.
2//!
3//! The `Object` class represents one of JavaScript's data types.
4//!
5//! It is used to store various keyed collections and more complex entities.
6//! Objects can be created using the `Object()` constructor or the
7//! object initializer / literal syntax.
8//!
9//! More information:
10//!  - [ECMAScript reference][spec]
11//!  - [MDN documentation][mdn]
12//!
13//! [spec]: https://tc39.es/ecma262/#sec-objects
14//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
15
16use crate::{
17    builtins::{BuiltIn, JsArgs},
18    context::StandardObjects,
19    object::{
20        internal_methods::get_prototype_from_constructor, ConstructorBuilder, IntegrityLevel,
21        JsObject, Object as BuiltinObject, ObjectData, ObjectInitializer, ObjectKind,
22    },
23    property::{Attribute, DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind},
24    symbol::WellKnownSymbols,
25    value::{JsValue, Type},
26    BoaProfiler, Context, JsResult,
27};
28
29use super::Array;
30
31pub mod for_in_iterator;
32#[cfg(test)]
33mod tests;
34
35/// The global JavaScript object.
36#[derive(Debug, Clone, Copy)]
37pub struct Object;
38
39impl BuiltIn for Object {
40    const NAME: &'static str = "Object";
41
42    fn attribute() -> Attribute {
43        Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
44    }
45
46    fn init(context: &mut Context) -> (&'static str, JsValue, Attribute) {
47        let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
48
49        let object = ConstructorBuilder::with_standard_object(
50            context,
51            Self::constructor,
52            context.standard_objects().object_object().clone(),
53        )
54        .name(Self::NAME)
55        .length(Self::LENGTH)
56        .inherit(JsValue::null())
57        .method(Self::has_own_property, "hasOwnProperty", 0)
58        .method(Self::property_is_enumerable, "propertyIsEnumerable", 0)
59        .method(Self::to_string, "toString", 0)
60        .method(Self::value_of, "valueOf", 0)
61        .method(Self::is_prototype_of, "isPrototypeOf", 0)
62        .static_method(Self::create, "create", 2)
63        .static_method(Self::set_prototype_of, "setPrototypeOf", 2)
64        .static_method(Self::get_prototype_of, "getPrototypeOf", 1)
65        .static_method(Self::define_property, "defineProperty", 3)
66        .static_method(Self::define_properties, "defineProperties", 2)
67        .static_method(Self::assign, "assign", 2)
68        .static_method(Self::is, "is", 2)
69        .static_method(Self::keys, "keys", 1)
70        .static_method(Self::values, "values", 1)
71        .static_method(Self::entries, "entries", 1)
72        .static_method(Self::seal, "seal", 1)
73        .static_method(Self::is_sealed, "isSealed", 1)
74        .static_method(Self::freeze, "freeze", 1)
75        .static_method(Self::is_frozen, "isFrozen", 1)
76        .static_method(Self::prevent_extensions, "preventExtensions", 1)
77        .static_method(Self::is_extensible, "isExtensible", 1)
78        .static_method(
79            Self::get_own_property_descriptor,
80            "getOwnPropertyDescriptor",
81            2,
82        )
83        .static_method(
84            Self::get_own_property_descriptors,
85            "getOwnPropertyDescriptors",
86            1,
87        )
88        .build();
89
90        (Self::NAME, object.into(), Self::attribute())
91    }
92}
93
94impl Object {
95    const LENGTH: usize = 1;
96
97    fn constructor(
98        new_target: &JsValue,
99        args: &[JsValue],
100        context: &mut Context,
101    ) -> JsResult<JsValue> {
102        if !new_target.is_undefined() {
103            let prototype = get_prototype_from_constructor(
104                new_target,
105                StandardObjects::object_object,
106                context,
107            )?;
108            let object = JsValue::new_object(context);
109
110            object
111                .as_object()
112                .expect("this should be an object")
113                .set_prototype_instance(prototype.into());
114            return Ok(object);
115        }
116        if let Some(arg) = args.get(0) {
117            if !arg.is_null_or_undefined() {
118                return Ok(arg.to_object(context)?.into());
119            }
120        }
121        Ok(JsValue::new_object(context))
122    }
123
124    /// `Object.create( proto, [propertiesObject] )`
125    ///
126    /// Creates a new object from the provided prototype.
127    ///
128    /// More information:
129    ///  - [ECMAScript reference][spec]
130    ///  - [MDN documentation][mdn]
131    ///
132    /// [spec]: https://tc39.es/ecma262/#sec-object.create
133    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
134    pub fn create(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
135        let prototype = args.get_or_undefined(0);
136        let properties = args.get_or_undefined(1);
137
138        let obj = match prototype {
139            JsValue::Object(_) | JsValue::Null => JsObject::new(BuiltinObject::with_prototype(
140                prototype.clone(),
141                ObjectData::ordinary(),
142            )),
143            _ => {
144                return context.throw_type_error(format!(
145                    "Object prototype may only be an Object or null: {}",
146                    prototype.display()
147                ))
148            }
149        };
150
151        if !properties.is_undefined() {
152            object_define_properties(&obj, properties.clone(), context)?;
153            return Ok(obj.into());
154        }
155
156        Ok(obj.into())
157    }
158
159    /// `Object.getOwnPropertyDescriptor( object, property )`
160    ///
161    /// Returns an object describing the configuration of a specific property on a given object.
162    ///
163    /// More information:
164    ///  - [ECMAScript reference][spec]
165    ///  - [MDN documentation][mdn]
166    ///
167    /// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
168    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
169    pub fn get_own_property_descriptor(
170        _: &JsValue,
171        args: &[JsValue],
172        context: &mut Context,
173    ) -> JsResult<JsValue> {
174        let object = args.get_or_undefined(0).to_object(context)?;
175        if let Some(key) = args.get(1) {
176            let key = key.to_property_key(context)?;
177
178            if let Some(desc) = object.__get_own_property__(&key, context)? {
179                return Ok(Self::from_property_descriptor(desc, context));
180            }
181        }
182
183        Ok(JsValue::undefined())
184    }
185
186    /// `Object.getOwnPropertyDescriptors( object )`
187    ///
188    /// Returns all own property descriptors of a given object.
189    ///
190    /// More information:
191    ///  - [ECMAScript reference][spec]
192    ///  - [MDN documentation][mdn]
193    ///
194    /// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertydescriptors
195    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors
196    pub fn get_own_property_descriptors(
197        _: &JsValue,
198        args: &[JsValue],
199        context: &mut Context,
200    ) -> JsResult<JsValue> {
201        let object = args
202            .get(0)
203            .unwrap_or(&JsValue::undefined())
204            .to_object(context)?;
205        let descriptors = context.construct_object();
206
207        for key in object.borrow().properties().keys() {
208            let descriptor = {
209                let desc = object
210                    .__get_own_property__(&key, context)?
211                    .expect("Expected property to be on object.");
212                Self::from_property_descriptor(desc, context)
213            };
214
215            if !descriptor.is_undefined() {
216                descriptors.borrow_mut().insert(
217                    key,
218                    PropertyDescriptor::builder()
219                        .value(descriptor)
220                        .writable(true)
221                        .enumerable(true)
222                        .configurable(true),
223                );
224            }
225        }
226
227        Ok(JsValue::Object(descriptors))
228    }
229
230    /// The abstract operation `FromPropertyDescriptor`.
231    ///
232    /// [ECMAScript reference][spec]
233    ///
234    /// [spec]: https://tc39.es/ecma262/#sec-frompropertydescriptor
235    fn from_property_descriptor(desc: PropertyDescriptor, context: &mut Context) -> JsValue {
236        let mut descriptor = ObjectInitializer::new(context);
237
238        // TODO: use CreateDataPropertyOrThrow
239
240        match desc.kind() {
241            DescriptorKind::Data { value, writable } => {
242                if let Some(value) = value {
243                    descriptor.property("value", value.clone(), Attribute::all());
244                }
245                if let Some(writable) = writable {
246                    descriptor.property("writable", *writable, Attribute::all());
247                }
248            }
249            DescriptorKind::Accessor { get, set } => {
250                if let Some(get) = get {
251                    descriptor.property("get", get.clone(), Attribute::all());
252                }
253                if let Some(set) = set {
254                    descriptor.property("set", set.clone(), Attribute::all());
255                }
256            }
257            _ => {}
258        }
259
260        if let Some(enumerable) = desc.enumerable() {
261            descriptor.property("enumerable", enumerable, Attribute::all());
262        }
263
264        if let Some(configurable) = desc.configurable() {
265            descriptor.property("configurable", configurable, Attribute::all());
266        }
267
268        descriptor.build().into()
269    }
270
271    /// Uses the SameValue algorithm to check equality of objects
272    pub fn is(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
273        let x = args.get_or_undefined(0);
274        let y = args.get_or_undefined(1);
275
276        Ok(JsValue::same_value(x, y).into())
277    }
278
279    /// Get the `prototype` of an object.
280    pub fn get_prototype_of(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue> {
281        if args.is_empty() {
282            return ctx.throw_type_error(
283                "Object.getPrototypeOf: At least 1 argument required, but only 0 passed",
284            );
285        }
286
287        // 1. Let obj be ? ToObject(O).
288        let obj = args[0].clone().to_object(ctx)?;
289
290        // 2. Return ? obj.[[GetPrototypeOf]]().
291        Ok(obj.prototype_instance())
292    }
293
294    /// Set the `prototype` of an object.
295    ///
296    /// [More information][spec]
297    ///
298    /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof
299    pub fn set_prototype_of(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue> {
300        if args.len() < 2 {
301            return ctx.throw_type_error(format!(
302                "Object.setPrototypeOf: At least 2 arguments required, but only {} passed",
303                args.len()
304            ));
305        }
306
307        // 1. Set O to ? RequireObjectCoercible(O).
308        let obj = args
309            .get(0)
310            .cloned()
311            .unwrap_or_default()
312            .require_object_coercible(ctx)?
313            .clone();
314
315        // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception.
316        let proto = args.get_or_undefined(1);
317        if !matches!(proto.get_type(), Type::Object | Type::Null) {
318            return ctx.throw_type_error(format!(
319                "expected an object or null, got {}",
320                proto.type_of()
321            ));
322        }
323
324        // 3. If Type(O) is not Object, return O.
325        if !obj.is_object() {
326            return Ok(obj);
327        }
328
329        // 4. Let status be ? O.[[SetPrototypeOf]](proto).
330        let status = obj
331            .as_object()
332            .expect("obj was not an object")
333            .__set_prototype_of__(proto.clone(), ctx)?;
334
335        // 5. If status is false, throw a TypeError exception.
336        if !status {
337            return ctx.throw_type_error("can't set prototype of this object");
338        }
339
340        // 6. Return O.
341        Ok(obj)
342    }
343
344    /// `Object.prototype.isPrototypeOf( proto )`
345    ///
346    /// Check whether or not an object exists within another object's prototype chain.
347    ///
348    /// More information:
349    ///  - [ECMAScript reference][spec]
350    ///  - [MDN documentation][mdn]
351    ///
352    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.isprototypeof
353    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isPrototypeOf
354    pub fn is_prototype_of(
355        this: &JsValue,
356        args: &[JsValue],
357        context: &mut Context,
358    ) -> JsResult<JsValue> {
359        let v = args.get_or_undefined(0);
360        if !v.is_object() {
361            return Ok(JsValue::new(false));
362        }
363        let mut v = v.clone();
364        let o = JsValue::new(this.to_object(context)?);
365        loop {
366            v = Self::get_prototype_of(this, &[v], context)?;
367            if v.is_null() {
368                return Ok(JsValue::new(false));
369            }
370            if JsValue::same_value(&o, &v) {
371                return Ok(JsValue::new(true));
372            }
373        }
374    }
375
376    /// Define a property in an object
377    pub fn define_property(
378        _: &JsValue,
379        args: &[JsValue],
380        context: &mut Context,
381    ) -> JsResult<JsValue> {
382        let object = args.get_or_undefined(0);
383        if let Some(object) = object.as_object() {
384            let key = args
385                .get(1)
386                .unwrap_or(&JsValue::Undefined)
387                .to_property_key(context)?;
388            let desc = args
389                .get(2)
390                .unwrap_or(&JsValue::Undefined)
391                .to_property_descriptor(context)?;
392
393            object.define_property_or_throw(key, desc, context)?;
394
395            Ok(object.into())
396        } else {
397            context.throw_type_error("Object.defineProperty called on non-object")
398        }
399    }
400
401    /// `Object.defineProperties( proto, [propertiesObject] )`
402    ///
403    /// Creates or update own properties to the object
404    ///
405    /// More information:
406    ///  - [ECMAScript reference][spec]
407    ///  - [MDN documentation][mdn]
408    ///
409    /// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties
410    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties
411    pub fn define_properties(
412        _: &JsValue,
413        args: &[JsValue],
414        context: &mut Context,
415    ) -> JsResult<JsValue> {
416        let arg = args.get_or_undefined(0);
417        let arg_obj = arg.as_object();
418        if let Some(obj) = arg_obj {
419            let props = args.get_or_undefined(1);
420            object_define_properties(&obj, props.clone(), context)?;
421            Ok(arg.clone())
422        } else {
423            context.throw_type_error("Expected an object")
424        }
425    }
426
427    /// `Object.prototype.valueOf()`
428    ///
429    /// More information:
430    ///  - [ECMAScript reference][spec]
431    ///  - [MDN documentation][mdn]
432    ///
433    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.valueof
434    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf
435    pub fn value_of(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
436        // 1. Return ? ToObject(this value).
437        Ok(this.to_object(context)?.into())
438    }
439
440    /// `Object.prototype.toString()`
441    ///
442    /// This method returns a string representing the object.
443    ///
444    /// More information:
445    ///  - [ECMAScript reference][spec]
446    ///  - [MDN documentation][mdn]
447    ///
448    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.tostring
449    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
450    #[allow(clippy::wrong_self_convention)]
451    pub fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
452        // 1. If the this value is undefined, return "[object Undefined]".
453        if this.is_undefined() {
454            return Ok("[object Undefined]".into());
455        }
456        // 2. If the this value is null, return "[object Null]".
457        if this.is_null() {
458            return Ok("[object Null]".into());
459        }
460        // 3. Let O be ! ToObject(this value).
461        let o = this.to_object(context)?;
462        // TODO: 4. Let isArray be ? IsArray(O).
463        // TODO: 5. If isArray is true, let builtinTag be "Array".
464
465        // 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments".
466        // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function".
467        // 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error".
468        // 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".
469        // 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number".
470        // 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String".
471        // 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date".
472        // 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".
473        // 14. Else, let builtinTag be "Object".
474        let builtin_tag = {
475            let o = o.borrow();
476            match o.kind() {
477                ObjectKind::Array => "Array",
478                // TODO: Arguments Exotic Objects are currently not supported
479                ObjectKind::Function(_) => "Function",
480                ObjectKind::Error => "Error",
481                ObjectKind::Boolean(_) => "Boolean",
482                ObjectKind::Number(_) => "Number",
483                ObjectKind::String(_) => "String",
484                ObjectKind::Date(_) => "Date",
485                ObjectKind::RegExp(_) => "RegExp",
486                _ => "Object",
487            }
488        };
489
490        // 15. Let tag be ? Get(O, @@toStringTag).
491        let tag = o.get(WellKnownSymbols::to_string_tag(), context)?;
492
493        // 16. If Type(tag) is not String, set tag to builtinTag.
494        let tag_str = tag.as_string().map(|s| s.as_str()).unwrap_or(builtin_tag);
495
496        // 17. Return the string-concatenation of "[object ", tag, and "]".
497        Ok(format!("[object {}]", tag_str).into())
498    }
499
500    /// `Object.prototype.hasOwnPrototype( property )`
501    ///
502    /// The method returns a boolean indicating whether the object has the specified property
503    /// as its own property (as opposed to inheriting it).
504    ///
505    /// More information:
506    ///  - [ECMAScript reference][spec]
507    ///  - [MDN documentation][mdn]
508    ///
509    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.hasownproperty
510    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
511    pub fn has_own_property(
512        this: &JsValue,
513        args: &[JsValue],
514        context: &mut Context,
515    ) -> JsResult<JsValue> {
516        let key = args
517            .get(0)
518            .unwrap_or(&JsValue::undefined())
519            .to_property_key(context)?;
520        let object = this.to_object(context)?;
521
522        Ok(object.has_own_property(key, context)?.into())
523    }
524
525    /// `Object.prototype.propertyIsEnumerable( property )`
526    ///
527    /// This method returns a Boolean indicating whether the specified property is
528    /// enumerable and is the object's own property.
529    ///
530    /// More information:
531    ///  - [ECMAScript reference][spec]
532    ///  - [MDN documentation][mdn]
533    ///
534    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable
535    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable
536    pub fn property_is_enumerable(
537        this: &JsValue,
538        args: &[JsValue],
539        context: &mut Context,
540    ) -> JsResult<JsValue> {
541        let key = match args.get(0) {
542            None => return Ok(JsValue::new(false)),
543            Some(key) => key,
544        };
545
546        let key = key.to_property_key(context)?;
547        let own_property = this
548            .to_object(context)?
549            .__get_own_property__(&key, context)?;
550
551        Ok(own_property.map_or(JsValue::new(false), |own_prop| {
552            JsValue::new(own_prop.enumerable())
553        }))
554    }
555
556    /// `Object.assign( target, ...sources )`
557    ///
558    /// This method copies all enumerable own properties from one or more
559    /// source objects to a target object. It returns the target object.
560    ///
561    /// More information:
562    ///  - [ECMAScript reference][spec]
563    ///  - [MDN documentation][mdn]
564    ///
565    /// [spec]: https://tc39.es/ecma262/#sec-object.assign
566    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
567    pub fn assign(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
568        // 1. Let to be ? ToObject(target).
569        let to = args.get_or_undefined(0).to_object(context)?;
570
571        // 2. If only one argument was passed, return to.
572        if args.len() == 1 {
573            return Ok(to.into());
574        }
575
576        // 3. For each element nextSource of sources, do
577        for source in &args[1..] {
578            // 3.a. If nextSource is neither undefined nor null, then
579            if !source.is_null_or_undefined() {
580                // 3.a.i. Let from be ! ToObject(nextSource).
581                let from = source.to_object(context).unwrap();
582                // 3.a.ii. Let keys be ? from.[[OwnPropertyKeys]]().
583                let keys = from.__own_property_keys__(context)?;
584                // 3.a.iii. For each element nextKey of keys, do
585                for key in keys {
586                    // 3.a.iii.1. Let desc be ? from.[[GetOwnProperty]](nextKey).
587                    if let Some(desc) = from.__get_own_property__(&key, context)? {
588                        // 3.a.iii.2. If desc is not undefined and desc.[[Enumerable]] is true, then
589                        if desc.expect_enumerable() {
590                            // 3.a.iii.2.a. Let propValue be ? Get(from, nextKey).
591                            let property = from.get(key.clone(), context)?;
592                            // 3.a.iii.2.b. Perform ? Set(to, nextKey, propValue, true).
593                            to.set(key, property, true, context)?;
594                        }
595                    }
596                }
597            }
598        }
599
600        // 4. Return to.
601        Ok(to.into())
602    }
603
604    /// `Object.keys( target )`
605    ///
606    /// This method returns an array of a given object's own enumerable
607    /// property names, iterated in the same order that a normal loop would.
608    ///
609    /// More information:
610    ///  - [ECMAScript reference][spec]
611    ///  - [MDN documentation][mdn]
612    ///
613    /// [spec]: https://tc39.es/ecma262/#sec-object.keys
614    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
615    pub fn keys(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
616        // 1. Let obj be ? ToObject(target).
617        let obj = args
618            .get(0)
619            .cloned()
620            .unwrap_or_default()
621            .to_object(context)?;
622
623        // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key).
624        let name_list = obj.enumerable_own_property_names(PropertyNameKind::Key, context)?;
625
626        // 3. Return CreateArrayFromList(nameList).
627        let result = Array::create_array_from_list(name_list, context);
628
629        Ok(result.into())
630    }
631
632    /// `Object.values( target )`
633    ///
634    /// More information:
635    ///  - [ECMAScript reference][spec]
636    ///  - [MDN documentation][mdn]
637    ///
638    /// [spec]: https://tc39.es/ecma262/#sec-object.values
639    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
640    pub fn values(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
641        // 1. Let obj be ? ToObject(target).
642        let obj = args
643            .get(0)
644            .cloned()
645            .unwrap_or_default()
646            .to_object(context)?;
647
648        // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, value).
649        let name_list = obj.enumerable_own_property_names(PropertyNameKind::Value, context)?;
650
651        // 3. Return CreateArrayFromList(nameList).
652        let result = Array::create_array_from_list(name_list, context);
653
654        Ok(result.into())
655    }
656
657    /// `Object.entries( target )`
658    ///
659    /// This method returns an array of a given object's own enumerable string-keyed property [key, value] pairs.
660    /// This is the same as iterating with a for...in loop,
661    /// except that a for...in loop enumerates properties in the prototype chain as well).
662    ///
663    /// More information:
664    ///  - [ECMAScript reference][spec]
665    ///  - [MDN documentation][mdn]
666    ///
667    /// [spec]: https://tc39.es/ecma262/#sec-object.entries
668    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
669    pub fn entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
670        // 1. Let obj be ? ToObject(target).
671        let obj = args
672            .get(0)
673            .cloned()
674            .unwrap_or_default()
675            .to_object(context)?;
676
677        // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key+value).
678        let name_list =
679            obj.enumerable_own_property_names(PropertyNameKind::KeyAndValue, context)?;
680
681        // 3. Return CreateArrayFromList(nameList).
682        let result = Array::create_array_from_list(name_list, context);
683
684        Ok(result.into())
685    }
686
687    /// `Object.seal( target )`
688    ///
689    /// More information:
690    ///  - [ECMAScript reference][spec]
691    ///  - [MDN documentation][mdn]
692    ///
693    /// [spec]: https://tc39.es/ecma262/#sec-object.seal
694    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal
695    pub fn seal(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
696        let o = args.get_or_undefined(0);
697
698        if let Some(o) = o.as_object() {
699            // 2. Let status be ? SetIntegrityLevel(O, sealed).
700            let status = o.set_integrity_level(IntegrityLevel::Sealed, context)?;
701            // 3. If status is false, throw a TypeError exception.
702            if !status {
703                return context.throw_type_error("cannot seal object");
704            }
705        }
706        // 1. If Type(O) is not Object, return O.
707        // 4. Return O.
708        Ok(o.clone())
709    }
710
711    /// `Object.isSealed( target )`
712    ///
713    /// More information:
714    ///  - [ECMAScript reference][spec]
715    ///  - [MDN documentation][mdn]
716    ///
717    /// [spec]: https://tc39.es/ecma262/#sec-object.issealed
718    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed
719    pub fn is_sealed(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
720        let o = args.get_or_undefined(0);
721
722        // 1. If Type(O) is not Object, return true.
723        // 2. Return ? TestIntegrityLevel(O, sealed).
724        if let Some(o) = o.as_object() {
725            Ok(o.test_integrity_level(IntegrityLevel::Sealed, context)?
726                .into())
727        } else {
728            Ok(JsValue::new(true))
729        }
730    }
731
732    /// `Object.freeze( target )`
733    ///
734    /// More information:
735    ///  - [ECMAScript reference][spec]
736    ///  - [MDN documentation][mdn]
737    ///
738    /// [spec]: https://tc39.es/ecma262/#sec-object.freeze
739    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
740    pub fn freeze(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
741        let o = args.get_or_undefined(0);
742
743        if let Some(o) = o.as_object() {
744            // 2. Let status be ? SetIntegrityLevel(O, frozen).
745            let status = o.set_integrity_level(IntegrityLevel::Frozen, context)?;
746            // 3. If status is false, throw a TypeError exception.
747            if !status {
748                return context.throw_type_error("cannot freeze object");
749            }
750        }
751        // 1. If Type(O) is not Object, return O.
752        // 4. Return O.
753        Ok(o.clone())
754    }
755
756    /// `Object.isFrozen( target )`
757    ///
758    /// More information:
759    ///  - [ECMAScript reference][spec]
760    ///  - [MDN documentation][mdn]
761    ///
762    /// [spec]: https://tc39.es/ecma262/#sec-object.isfrozen
763    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen
764    pub fn is_frozen(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
765        let o = args.get_or_undefined(0);
766
767        // 1. If Type(O) is not Object, return true.
768        // 2. Return ? TestIntegrityLevel(O, frozen).
769        if let Some(o) = o.as_object() {
770            Ok(o.test_integrity_level(IntegrityLevel::Frozen, context)?
771                .into())
772        } else {
773            Ok(JsValue::new(true))
774        }
775    }
776
777    /// `Object.preventExtensions( target )`
778    ///
779    /// More information:
780    ///  - [ECMAScript reference][spec]
781    ///  - [MDN documentation][mdn]
782    ///
783    /// [spec]: https://tc39.es/ecma262/#sec-object.preventextensions
784    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions
785    pub fn prevent_extensions(
786        _: &JsValue,
787        args: &[JsValue],
788        context: &mut Context,
789    ) -> JsResult<JsValue> {
790        let o = args.get_or_undefined(0);
791
792        if let Some(o) = o.as_object() {
793            // 2. Let status be ? O.[[PreventExtensions]]().
794            let status = o.__prevent_extensions__(context)?;
795            // 3. If status is false, throw a TypeError exception.
796            if !status {
797                return context.throw_type_error("cannot prevent extensions");
798            }
799        }
800        // 1. If Type(O) is not Object, return O.
801        // 4. Return O.
802        Ok(o.clone())
803    }
804
805    /// `Object.isExtensible( target )`
806    ///
807    /// More information:
808    ///  - [ECMAScript reference][spec]
809    ///  - [MDN documentation][mdn]
810    ///
811    /// [spec]: https://tc39.es/ecma262/#sec-object.isextensible
812    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible
813    pub fn is_extensible(
814        _: &JsValue,
815        args: &[JsValue],
816        context: &mut Context,
817    ) -> JsResult<JsValue> {
818        let o = args.get_or_undefined(0);
819        // 1. If Type(O) is not Object, return false.
820        if let Some(o) = o.as_object() {
821            // 2. Return ? IsExtensible(O).
822            Ok(o.is_extensible(context)?.into())
823        } else {
824            Ok(JsValue::new(false))
825        }
826    }
827}
828
829/// The abstract operation ObjectDefineProperties
830///
831/// More information:
832///  - [ECMAScript reference][spec]
833///
834/// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties
835#[inline]
836fn object_define_properties(
837    object: &JsObject,
838    props: JsValue,
839    context: &mut Context,
840) -> JsResult<()> {
841    // 1. Assert: Type(O) is Object.
842    // 2. Let props be ? ToObject(Properties).
843    let props = &props.to_object(context)?;
844
845    // 3. Let keys be ? props.[[OwnPropertyKeys]]().
846    let keys = props.__own_property_keys__(context)?;
847
848    // 4. Let descriptors be a new empty List.
849    let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new();
850
851    // 5. For each element nextKey of keys, do
852    for next_key in keys {
853        // a. Let propDesc be ? props.[[GetOwnProperty]](nextKey).
854        // b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then
855        if let Some(prop_desc) = props.__get_own_property__(&next_key, context)? {
856            if prop_desc.expect_enumerable() {
857                // i. Let descObj be ? Get(props, nextKey).
858                let desc_obj = props.get(next_key.clone(), context)?;
859
860                // ii. Let desc be ? ToPropertyDescriptor(descObj).
861                let desc = desc_obj.to_property_descriptor(context)?;
862
863                // iii. Append the pair (a two element List) consisting of nextKey and desc to the end of descriptors.
864                descriptors.push((next_key, desc));
865            }
866        }
867    }
868
869    // 6. For each element pair of descriptors, do
870    // a. Let P be the first element of pair.
871    // b. Let desc be the second element of pair.
872    for (p, d) in descriptors {
873        // c. Perform ? DefinePropertyOrThrow(O, P, desc).
874        object.define_property_or_throw(p, d, context)?;
875    }
876
877    // 7. Return O.
878    Ok(())
879}