boa_engine/builtins/object/
mod.rs

1//! Boa's implementation of ECMAScript's global `Object` object.
2//!
3//! The `Object` class represents one of ECMAScript'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 super::{
17    Array, BuiltInBuilder, BuiltInConstructor, Date, IntrinsicObject, RegExp, error::Error,
18};
19use crate::builtins::function::arguments::{MappedArguments, UnmappedArguments};
20use crate::value::JsVariant;
21use crate::{
22    Context, JsArgs, JsData, JsResult, JsString,
23    builtins::{BuiltInObject, iterable::IteratorHint, map},
24    context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
25    error::JsNativeError,
26    js_string,
27    native_function::NativeFunction,
28    object::{
29        FunctionObjectBuilder, IntegrityLevel, JsObject,
30        internal_methods::{InternalMethodPropertyContext, get_prototype_from_constructor},
31    },
32    property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
33    realm::Realm,
34    string::StaticJsStrings,
35    symbol::JsSymbol,
36    value::JsValue,
37};
38use boa_gc::{Finalize, Trace};
39use boa_macros::js_str;
40use tap::{Conv, Pipe};
41
42pub(crate) mod for_in_iterator;
43#[cfg(test)]
44mod tests;
45
46/// An ordinary Javascript `Object`.
47#[derive(Debug, Default, Clone, Copy, Trace, Finalize, JsData)]
48#[boa_gc(empty_trace)]
49pub struct OrdinaryObject;
50
51impl IntrinsicObject for OrdinaryObject {
52    fn init(realm: &Realm) {
53        let legacy_proto_getter = BuiltInBuilder::callable(realm, Self::legacy_proto_getter)
54            .name(js_string!("get __proto__"))
55            .build();
56
57        let legacy_setter_proto = BuiltInBuilder::callable(realm, Self::legacy_proto_setter)
58            .name(js_string!("set __proto__"))
59            .build();
60
61        BuiltInBuilder::from_standard_constructor::<Self>(realm)
62            .inherits(None)
63            .accessor(
64                js_string!("__proto__"),
65                Some(legacy_proto_getter),
66                Some(legacy_setter_proto),
67                Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
68            )
69            .method(Self::has_own_property, js_string!("hasOwnProperty"), 1)
70            .method(
71                Self::property_is_enumerable,
72                js_string!("propertyIsEnumerable"),
73                1,
74            )
75            .method(Self::to_string, js_string!("toString"), 0)
76            .method(Self::to_locale_string, js_string!("toLocaleString"), 0)
77            .method(Self::value_of, js_string!("valueOf"), 0)
78            .method(Self::is_prototype_of, js_string!("isPrototypeOf"), 1)
79            .method(
80                Self::legacy_define_getter,
81                js_string!("__defineGetter__"),
82                2,
83            )
84            .method(
85                Self::legacy_define_setter,
86                js_string!("__defineSetter__"),
87                2,
88            )
89            .method(
90                Self::legacy_lookup_getter,
91                js_string!("__lookupGetter__"),
92                1,
93            )
94            .method(
95                Self::legacy_lookup_setter,
96                js_string!("__lookupSetter__"),
97                1,
98            )
99            .static_method(Self::create, js_string!("create"), 2)
100            .static_method(Self::set_prototype_of, js_string!("setPrototypeOf"), 2)
101            .static_method(Self::get_prototype_of, js_string!("getPrototypeOf"), 1)
102            .static_method(Self::define_property, js_string!("defineProperty"), 3)
103            .static_method(Self::define_properties, js_string!("defineProperties"), 2)
104            .static_method(Self::assign, js_string!("assign"), 2)
105            .static_method(Self::is, js_string!("is"), 2)
106            .static_method(Self::keys, js_string!("keys"), 1)
107            .static_method(Self::values, js_string!("values"), 1)
108            .static_method(Self::entries, js_string!("entries"), 1)
109            .static_method(Self::seal, js_string!("seal"), 1)
110            .static_method(Self::is_sealed, js_string!("isSealed"), 1)
111            .static_method(Self::freeze, js_string!("freeze"), 1)
112            .static_method(Self::is_frozen, js_string!("isFrozen"), 1)
113            .static_method(Self::prevent_extensions, js_string!("preventExtensions"), 1)
114            .static_method(Self::is_extensible, js_string!("isExtensible"), 1)
115            .static_method(
116                Self::get_own_property_descriptor,
117                js_string!("getOwnPropertyDescriptor"),
118                2,
119            )
120            .static_method(
121                Self::get_own_property_descriptors,
122                js_string!("getOwnPropertyDescriptors"),
123                1,
124            )
125            .static_method(
126                Self::get_own_property_names,
127                js_string!("getOwnPropertyNames"),
128                1,
129            )
130            .static_method(
131                Self::get_own_property_symbols,
132                js_string!("getOwnPropertySymbols"),
133                1,
134            )
135            .static_method(Self::has_own, js_string!("hasOwn"), 2)
136            .static_method(Self::from_entries, js_string!("fromEntries"), 1)
137            .static_method(Self::group_by, js_string!("groupBy"), 2)
138            .build();
139    }
140
141    fn get(intrinsics: &Intrinsics) -> JsObject {
142        Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
143    }
144}
145
146impl BuiltInObject for OrdinaryObject {
147    const NAME: JsString = StaticJsStrings::OBJECT;
148}
149
150impl BuiltInConstructor for OrdinaryObject {
151    const CONSTRUCTOR_ARGUMENTS: usize = 1;
152    const PROTOTYPE_STORAGE_SLOTS: usize = 12;
153    const CONSTRUCTOR_STORAGE_SLOTS: usize = 23;
154
155    const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
156        StandardConstructors::object;
157
158    fn constructor(
159        new_target: &JsValue,
160        args: &[JsValue],
161        context: &mut Context,
162    ) -> JsResult<JsValue> {
163        // 1. If NewTarget is neither undefined nor the active function object, then
164        if !new_target.is_undefined()
165            && new_target
166                != &context
167                    .active_function_object()
168                    .unwrap_or_else(|| context.intrinsics().constructors().object().constructor())
169                    .into()
170        {
171            //     a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%").
172            let prototype =
173                get_prototype_from_constructor(new_target, StandardConstructors::object, context)?;
174            let object = JsObject::from_proto_and_data_with_shared_shape(
175                context.root_shape(),
176                prototype,
177                OrdinaryObject,
178            );
179            return Ok(object.into());
180        }
181
182        let value = args.get_or_undefined(0);
183
184        // 2. If value is undefined or null, return OrdinaryObjectCreate(%Object.prototype%).
185        if value.is_null_or_undefined() {
186            Ok(JsObject::with_object_proto(context.intrinsics()).into())
187        } else {
188            // 3. Return ! ToObject(value).
189            value.to_object(context).map(JsValue::from)
190        }
191    }
192}
193
194impl OrdinaryObject {
195    /// `get Object.prototype.__proto__`
196    ///
197    /// The `__proto__` getter function exposes the value of the
198    /// internal `[[Prototype]]` of an object.
199    ///
200    /// More information:
201    ///  - [ECMAScript reference][spec]
202    ///  - [MDN documentation][mdn]
203    ///
204    /// [spec]: https://tc39.es/ecma262/#sec-get-object.prototype.__proto__
205    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
206    pub fn legacy_proto_getter(
207        this: &JsValue,
208        _: &[JsValue],
209        context: &mut Context,
210    ) -> JsResult<JsValue> {
211        // 1. Let O be ? ToObject(this value).
212        let obj = this.to_object(context)?;
213
214        // 2. Return ? O.[[GetPrototypeOf]]().
215        let proto = obj.__get_prototype_of__(&mut InternalMethodPropertyContext::new(context))?;
216
217        Ok(proto.map_or(JsValue::null(), JsValue::new))
218    }
219
220    /// `set Object.prototype.__proto__`
221    ///
222    /// The `__proto__` setter allows the `[[Prototype]]` of
223    /// an object to be mutated.
224    ///
225    /// More information:
226    ///  - [ECMAScript reference][spec]
227    ///  - [MDN documentation][mdn]
228    ///
229    /// [spec]: https://tc39.es/ecma262/#sec-set-object.prototype.__proto__
230    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
231    pub fn legacy_proto_setter(
232        this: &JsValue,
233        args: &[JsValue],
234        context: &mut Context,
235    ) -> JsResult<JsValue> {
236        // 1. Let O be ? RequireObjectCoercible(this value).
237        let this = this.require_object_coercible()?;
238
239        // 2. If Type(proto) is neither Object nor Null, return undefined.
240        let proto = match args.get_or_undefined(0).variant() {
241            JsVariant::Object(proto) => Some(proto.clone()),
242            JsVariant::Null => None,
243            _ => return Ok(JsValue::undefined()),
244        };
245
246        // 3. If Type(O) is not Object, return undefined.
247        let JsVariant::Object(object) = this.variant() else {
248            return Ok(JsValue::undefined());
249        };
250
251        // 4. Let status be ? O.[[SetPrototypeOf]](proto).
252        let status =
253            object.__set_prototype_of__(proto, &mut InternalMethodPropertyContext::new(context))?;
254
255        // 5. If status is false, throw a TypeError exception.
256        if !status {
257            return Err(JsNativeError::typ()
258                .with_message("__proto__ called on null or undefined")
259                .into());
260        }
261
262        // 6. Return undefined.
263        Ok(JsValue::undefined())
264    }
265
266    /// `Object.prototype.__defineGetter__(prop, func)`
267    ///
268    /// Binds an object's property to a function to be called when that property is looked up.
269    ///
270    /// More information:
271    ///  - [ECMAScript reference][spec]
272    ///  - [MDN documentation][mdn]
273    ///
274    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.__defineGetter__
275    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineGetter__
276    pub fn legacy_define_getter(
277        this: &JsValue,
278        args: &[JsValue],
279        context: &mut Context,
280    ) -> JsResult<JsValue> {
281        let getter = args.get_or_undefined(1);
282
283        // 1. Let O be ? ToObject(this value).
284        let obj = this.to_object(context)?;
285
286        // 2. If IsCallable(getter) is false, throw a TypeError exception.
287        if !getter.is_callable() {
288            return Err(JsNativeError::typ()
289                .with_message("Object.prototype.__defineGetter__: Expecting function")
290                .into());
291        }
292
293        // 3. Let desc be PropertyDescriptor { [[Get]]: getter, [[Enumerable]]: true, [[Configurable]]: true }.
294        let desc = PropertyDescriptor::builder()
295            .get(getter.clone())
296            .enumerable(true)
297            .configurable(true);
298
299        // 4. Let key be ? ToPropertyKey(P).
300        let key = args.get_or_undefined(0).to_property_key(context)?;
301
302        // 5. Perform ? DefinePropertyOrThrow(O, key, desc).
303        obj.define_property_or_throw(key, desc, context)?;
304
305        // 6. Return undefined.
306        Ok(JsValue::undefined())
307    }
308
309    /// `Object.prototype.__defineSetter__(prop, func)`
310    ///
311    /// Binds an object's property to a function to be called when an attempt is made to set that property.
312    ///
313    /// More information:
314    ///  - [ECMAScript reference][spec]
315    ///  - [MDN documentation][mdn]
316    ///
317    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.__defineSetter__
318    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__defineSetter__
319    pub fn legacy_define_setter(
320        this: &JsValue,
321        args: &[JsValue],
322        context: &mut Context,
323    ) -> JsResult<JsValue> {
324        let setter = args.get_or_undefined(1);
325
326        // 1. Let O be ? ToObject(this value).
327        let obj = this.to_object(context)?;
328
329        // 2. If IsCallable(setter) is false, throw a TypeError exception.
330        if !setter.is_callable() {
331            return Err(JsNativeError::typ()
332                .with_message("Object.prototype.__defineSetter__: Expecting function")
333                .into());
334        }
335
336        // 3. Let desc be PropertyDescriptor { [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: true }.
337        let desc = PropertyDescriptor::builder()
338            .set(setter.clone())
339            .enumerable(true)
340            .configurable(true);
341
342        // 4. Let key be ? ToPropertyKey(P).
343        let key = args.get_or_undefined(0).to_property_key(context)?;
344
345        // 5. Perform ? DefinePropertyOrThrow(O, key, desc).
346        obj.define_property_or_throw(key, desc, context)?;
347
348        // 6. Return undefined.
349        Ok(JsValue::undefined())
350    }
351
352    /// `Object.prototype.__lookupGetter__(prop)`
353    ///
354    /// Returns the function bound as a getter to the specified property.
355    ///
356    /// More information:
357    ///  - [ECMAScript reference][spec]
358    ///  - [MDN documentation][mdn]
359    ///
360    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.__lookupGetter__
361    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__lookupGetter__
362    pub fn legacy_lookup_getter(
363        this: &JsValue,
364        args: &[JsValue],
365        context: &mut Context,
366    ) -> JsResult<JsValue> {
367        // 1. Let O be ? ToObject(this value).
368        let mut obj = this.to_object(context)?;
369
370        // 2. Let key be ? ToPropertyKey(P).
371        let key = args.get_or_undefined(0).to_property_key(context)?;
372
373        // 3. Repeat
374        loop {
375            // a. Let desc be ? O.[[GetOwnProperty]](key).
376
377            let desc =
378                obj.__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
379
380            // b. If desc is not undefined, then
381            if let Some(current_desc) = desc {
382                // i. If IsAccessorDescriptor(desc) is true, return desc.[[Get]].
383                return if current_desc.is_accessor_descriptor() {
384                    Ok(current_desc.expect_get().clone())
385                } else {
386                    // ii. Return undefined.
387                    Ok(JsValue::undefined())
388                };
389            }
390            match obj.__get_prototype_of__(&mut InternalMethodPropertyContext::new(context))? {
391                // c. Set O to ? O.[[GetPrototypeOf]]().
392                Some(o) => obj = o,
393                // d. If O is null, return undefined.
394                None => return Ok(JsValue::undefined()),
395            }
396        }
397    }
398    /// `Object.prototype.__lookupSetter__(prop)`
399    ///
400    /// Returns the function bound as a getter to the specified property.
401    ///
402    /// More information:
403    ///  - [ECMAScript reference][spec]
404    ///  - [MDN documentation][mdn]
405    ///
406    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.__lookupSetter__
407    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/__lookupSetter__
408    pub fn legacy_lookup_setter(
409        this: &JsValue,
410        args: &[JsValue],
411        context: &mut Context,
412    ) -> JsResult<JsValue> {
413        // 1. Let O be ? ToObject(this value).
414        let mut obj = this.to_object(context)?;
415
416        // 2. Let key be ? ToPropertyKey(P).
417        let key = args.get_or_undefined(0).to_property_key(context)?;
418
419        // 3. Repeat
420        loop {
421            // a. Let desc be ? O.[[GetOwnProperty]](key).
422
423            let desc =
424                obj.__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
425
426            // b. If desc is not undefined, then
427            if let Some(current_desc) = desc {
428                // i. If IsAccessorDescriptor(desc) is true, return desc.[[Set]].
429                return if current_desc.is_accessor_descriptor() {
430                    Ok(current_desc.expect_set().clone())
431                } else {
432                    // ii. Return undefined.
433                    Ok(JsValue::undefined())
434                };
435            }
436            match obj.__get_prototype_of__(&mut InternalMethodPropertyContext::new(context))? {
437                // c. Set O to ? O.[[GetPrototypeOf]]().
438                Some(o) => obj = o,
439                // d. If O is null, return undefined.
440                None => return Ok(JsValue::undefined()),
441            }
442        }
443    }
444
445    /// `Object.create( proto, [propertiesObject] )`
446    ///
447    /// Creates a new object from the provided prototype.
448    ///
449    /// More information:
450    ///  - [ECMAScript reference][spec]
451    ///  - [MDN documentation][mdn]
452    ///
453    /// [spec]: https://tc39.es/ecma262/#sec-object.create
454    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
455    pub fn create(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
456        let prototype = args.get_or_undefined(0);
457        let properties = args.get_or_undefined(1);
458
459        let obj = match prototype.variant() {
460            JsVariant::Object(_) | JsVariant::Null => {
461                JsObject::from_proto_and_data_with_shared_shape(
462                    context.root_shape(),
463                    prototype.as_object(),
464                    OrdinaryObject,
465                )
466            }
467            _ => {
468                return Err(JsNativeError::typ()
469                    .with_message(format!(
470                        "Object prototype may only be an Object or null: {}",
471                        prototype.display()
472                    ))
473                    .into());
474            }
475        };
476
477        if !properties.is_undefined() {
478            object_define_properties(&obj, properties, context)?;
479            return Ok(obj.into());
480        }
481
482        Ok(obj.into())
483    }
484
485    /// `Object.getOwnPropertyDescriptor( object, property )`
486    ///
487    /// Returns an object describing the configuration of a specific property on a given object.
488    ///
489    /// More information:
490    ///  - [ECMAScript reference][spec]
491    ///  - [MDN documentation][mdn]
492    ///
493    /// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
494    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor
495    pub fn get_own_property_descriptor(
496        _: &JsValue,
497        args: &[JsValue],
498        context: &mut Context,
499    ) -> JsResult<JsValue> {
500        // 1. Let obj be ? ToObject(O).
501        let obj = args.get_or_undefined(0).to_object(context)?;
502
503        // 2. Let key be ? ToPropertyKey(P).
504        let key = args.get_or_undefined(1).to_property_key(context)?;
505
506        // 3. Let desc be ? obj.[[GetOwnProperty]](key).
507
508        let desc =
509            obj.__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
510
511        // 4. Return FromPropertyDescriptor(desc).
512        Ok(Self::from_property_descriptor(desc, context))
513    }
514
515    /// `Object.getOwnPropertyDescriptors( object )`
516    ///
517    /// Returns all own property descriptors of a given object.
518    ///
519    /// More information:
520    ///  - [ECMAScript reference][spec]
521    ///  - [MDN documentation][mdn]
522    ///
523    /// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertydescriptors
524    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors
525    pub fn get_own_property_descriptors(
526        _: &JsValue,
527        args: &[JsValue],
528        context: &mut Context,
529    ) -> JsResult<JsValue> {
530        // 1. Let obj be ? ToObject(O).
531        let obj = args.get_or_undefined(0).to_object(context)?;
532
533        // 2. Let ownKeys be ? obj.[[OwnPropertyKeys]]().
534        let own_keys =
535            obj.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
536
537        // 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%).
538        let descriptors = JsObject::with_object_proto(context.intrinsics());
539
540        // 4. For each element key of ownKeys, do
541        for key in own_keys {
542            // a. Let desc be ? obj.[[GetOwnProperty]](key).
543
544            let desc =
545                obj.__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
546
547            // b. Let descriptor be FromPropertyDescriptor(desc).
548            let descriptor = Self::from_property_descriptor(desc, context);
549
550            // c. If descriptor is not undefined,
551            //    perform ! CreateDataPropertyOrThrow(descriptors, key, descriptor).
552            if !descriptor.is_undefined() {
553                descriptors
554                    .create_data_property_or_throw(key, descriptor, context)
555                    .expect("should not fail according to spec");
556            }
557        }
558
559        // 5. Return descriptors.
560        Ok(descriptors.into())
561    }
562
563    /// The abstract operation `FromPropertyDescriptor`.
564    ///
565    /// [ECMAScript reference][spec]
566    ///
567    /// [spec]: https://tc39.es/ecma262/#sec-frompropertydescriptor
568    pub(crate) fn from_property_descriptor(
569        desc: Option<PropertyDescriptor>,
570        context: &mut Context,
571    ) -> JsValue {
572        // 1. If Desc is undefined, return undefined.
573        let Some(desc) = desc else {
574            return JsValue::undefined();
575        };
576
577        // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
578        // 3. Assert: obj is an extensible ordinary object with no own properties.
579        let obj = JsObject::with_object_proto(context.intrinsics());
580
581        // 4. If Desc has a [[Value]] field, then
582        if let Some(value) = desc.value() {
583            // a. Perform ! CreateDataPropertyOrThrow(obj, "value", Desc.[[Value]]).
584            obj.create_data_property_or_throw(js_string!("value"), value.clone(), context)
585                .expect("CreateDataPropertyOrThrow cannot fail here");
586        }
587
588        // 5. If Desc has a [[Writable]] field, then
589        if let Some(writable) = desc.writable() {
590            // a. Perform ! CreateDataPropertyOrThrow(obj, "writable", Desc.[[Writable]]).
591            obj.create_data_property_or_throw(js_string!("writable"), writable, context)
592                .expect("CreateDataPropertyOrThrow cannot fail here");
593        }
594
595        // 6. If Desc has a [[Get]] field, then
596        if let Some(get) = desc.get() {
597            // a. Perform ! CreateDataPropertyOrThrow(obj, "get", Desc.[[Get]]).
598            obj.create_data_property_or_throw(js_string!("get"), get.clone(), context)
599                .expect("CreateDataPropertyOrThrow cannot fail here");
600        }
601
602        // 7. If Desc has a [[Set]] field, then
603        if let Some(set) = desc.set() {
604            // a. Perform ! CreateDataPropertyOrThrow(obj, "set", Desc.[[Set]]).
605            obj.create_data_property_or_throw(js_string!("set"), set.clone(), context)
606                .expect("CreateDataPropertyOrThrow cannot fail here");
607        }
608
609        // 8. If Desc has an [[Enumerable]] field, then
610        if let Some(enumerable) = desc.enumerable() {
611            // a. Perform ! CreateDataPropertyOrThrow(obj, "enumerable", Desc.[[Enumerable]]).
612            obj.create_data_property_or_throw(js_string!("enumerable"), enumerable, context)
613                .expect("CreateDataPropertyOrThrow cannot fail here");
614        }
615
616        // 9. If Desc has a [[Configurable]] field, then
617        if let Some(configurable) = desc.configurable() {
618            // a. Perform ! CreateDataPropertyOrThrow(obj, "configurable", Desc.[[Configurable]]).
619            obj.create_data_property_or_throw(js_string!("configurable"), configurable, context)
620                .expect("CreateDataPropertyOrThrow cannot fail here");
621        }
622
623        // 10. Return obj.
624        obj.into()
625    }
626
627    /// Uses the `SameValue` algorithm to check equality of objects
628    pub fn is(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
629        let x = args.get_or_undefined(0);
630        let y = args.get_or_undefined(1);
631
632        Ok(JsValue::same_value(x, y).into())
633    }
634
635    /// Get the `prototype` of an object.
636    ///
637    /// [More information][spec]
638    ///
639    /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof
640    pub fn get_prototype_of(
641        _: &JsValue,
642        args: &[JsValue],
643        context: &mut Context,
644    ) -> JsResult<JsValue> {
645        if args.is_empty() {
646            return Err(JsNativeError::typ()
647                .with_message(
648                    "Object.getPrototypeOf: At least 1 argument required, but only 0 passed",
649                )
650                .into());
651        }
652
653        // 1. Let obj be ? ToObject(O).
654        let obj = args[0].clone().to_object(context)?;
655
656        // 2. Return ? obj.[[GetPrototypeOf]]().
657        Ok(obj
658            .__get_prototype_of__(&mut InternalMethodPropertyContext::new(context))?
659            .map_or(JsValue::null(), JsValue::new))
660    }
661
662    /// Set the `prototype` of an object.
663    ///
664    /// [More information][spec]
665    ///
666    /// [spec]: https://tc39.es/ecma262/#sec-object.setprototypeof
667    pub fn set_prototype_of(
668        _: &JsValue,
669        args: &[JsValue],
670        context: &mut Context,
671    ) -> JsResult<JsValue> {
672        if args.len() < 2 {
673            return Err(JsNativeError::typ()
674                .with_message(format!(
675                    "Object.setPrototypeOf: At least 2 arguments required, but only {} passed",
676                    args.len()
677                ))
678                .into());
679        }
680
681        // 1. Set O to ? RequireObjectCoercible(O).
682        let o = args
683            .first()
684            .cloned()
685            .unwrap_or_default()
686            .require_object_coercible()?
687            .clone();
688
689        let proto = match args.get_or_undefined(1).variant() {
690            JsVariant::Object(obj) => Some(obj.clone()),
691            JsVariant::Null => None,
692            // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception.
693            val => {
694                return Err(JsNativeError::typ()
695                    .with_message(format!(
696                        "expected an object or null, got `{}`",
697                        val.type_of()
698                    ))
699                    .into());
700            }
701        };
702
703        let Some(obj) = o.as_object() else {
704            // 3. If Type(O) is not Object, return O.
705            return Ok(o);
706        };
707
708        // 4. Let status be ? O.[[SetPrototypeOf]](proto).
709        let status =
710            obj.__set_prototype_of__(proto, &mut InternalMethodPropertyContext::new(context))?;
711
712        // 5. If status is false, throw a TypeError exception.
713        if !status {
714            return Err(JsNativeError::typ()
715                .with_message("can't set prototype of this object")
716                .into());
717        }
718
719        // 6. Return O.
720        Ok(o)
721    }
722
723    /// `Object.prototype.isPrototypeOf( proto )`
724    ///
725    /// Check whether or not an object exists within another object's prototype chain.
726    ///
727    /// More information:
728    ///  - [ECMAScript reference][spec]
729    ///  - [MDN documentation][mdn]
730    ///
731    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.isprototypeof
732    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isPrototypeOf
733    pub fn is_prototype_of(
734        this: &JsValue,
735        args: &[JsValue],
736        context: &mut Context,
737    ) -> JsResult<JsValue> {
738        let v = args.get_or_undefined(0);
739        if !v.is_object() {
740            return Ok(JsValue::new(false));
741        }
742        let mut v = v.clone();
743        let o = JsValue::new(this.to_object(context)?);
744        loop {
745            v = Self::get_prototype_of(this, &[v], context)?;
746            if v.is_null() {
747                return Ok(JsValue::new(false));
748            }
749            if JsValue::same_value(&o, &v) {
750                return Ok(JsValue::new(true));
751            }
752        }
753    }
754
755    /// Define a property in an object
756    pub fn define_property(
757        _: &JsValue,
758        args: &[JsValue],
759        context: &mut Context,
760    ) -> JsResult<JsValue> {
761        if let Some(object) = args.get_or_undefined(0).as_object() {
762            let key = args
763                .get(1)
764                .unwrap_or(&JsValue::undefined())
765                .to_property_key(context)?;
766            let desc = args
767                .get(2)
768                .unwrap_or(&JsValue::undefined())
769                .to_property_descriptor(context)?;
770
771            object.define_property_or_throw(key, desc, context)?;
772
773            Ok(object.clone().into())
774        } else {
775            Err(JsNativeError::typ()
776                .with_message("Object.defineProperty called on non-object")
777                .into())
778        }
779    }
780
781    /// `Object.defineProperties( proto, [propertiesObject] )`
782    ///
783    /// Creates or update own properties to the object
784    ///
785    /// More information:
786    ///  - [ECMAScript reference][spec]
787    ///  - [MDN documentation][mdn]
788    ///
789    /// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties
790    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties
791    pub fn define_properties(
792        _: &JsValue,
793        args: &[JsValue],
794        context: &mut Context,
795    ) -> JsResult<JsValue> {
796        let arg = args.get_or_undefined(0);
797        if let Some(obj) = arg.as_object() {
798            let props = args.get_or_undefined(1);
799            object_define_properties(&obj, props, context)?;
800            Ok(arg.clone())
801        } else {
802            Err(JsNativeError::typ()
803                .with_message("Expected an object")
804                .into())
805        }
806    }
807
808    /// `Object.prototype.valueOf()`
809    ///
810    /// More information:
811    ///  - [ECMAScript reference][spec]
812    ///  - [MDN documentation][mdn]
813    ///
814    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.valueof
815    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf
816    pub fn value_of(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
817        // 1. Return ? ToObject(this value).
818        Ok(this.to_object(context)?.into())
819    }
820
821    /// `Object.prototype.toString()`
822    ///
823    /// This method returns a string representing the object.
824    ///
825    /// More information:
826    ///  - [ECMAScript reference][spec]
827    ///  - [MDN documentation][mdn]
828    ///
829    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.tostring
830    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
831    #[allow(clippy::wrong_self_convention)]
832    pub fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
833        // 1. If the this value is undefined, return "[object Undefined]".
834        if this.is_undefined() {
835            return Ok(js_string!("[object Undefined]").into());
836        }
837        // 2. If the this value is null, return "[object Null]".
838        if this.is_null() {
839            return Ok(js_string!("[object Null]").into());
840        }
841        // 3. Let O be ! ToObject(this value).
842        let o = this.to_object(context).expect("toObject cannot fail here");
843
844        //  4. Let isArray be ? IsArray(O).
845        //  5. If isArray is true, let builtinTag be "Array".
846        let builtin_tag = if o.is_array_abstract()? {
847            js_str!("Array")
848        } else if o.is::<UnmappedArguments>() || o.is::<MappedArguments>() {
849            // 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments".
850            js_str!("Arguments")
851        } else if o.is_callable() {
852            // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function".
853            js_str!("Function")
854        } else if o.is::<Error>() {
855            // 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error".
856            js_str!("Error")
857        } else if o.is::<bool>() {
858            // 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".
859            js_str!("Boolean")
860        } else if o.is::<f64>() {
861            // 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number".
862            js_str!("Number")
863        } else if o.is::<JsString>() {
864            // 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String".
865            js_str!("String")
866        } else if o.is::<Date>() {
867            // 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date".
868            js_str!("Date")
869        } else if o.is::<RegExp>() {
870            // 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".
871            js_str!("RegExp")
872        } else {
873            // 14. Else, let builtinTag be "Object".
874            js_str!("Object")
875        };
876
877        // 15. Let tag be ? Get(O, @@toStringTag).
878        let tag = o.get(JsSymbol::to_string_tag(), context)?;
879
880        // 16. If Type(tag) is not String, set tag to builtinTag.
881        let tag = tag.as_string();
882        let tag = tag.as_ref().map_or(builtin_tag, JsString::as_str);
883
884        // 17. Return the string-concatenation of "[object ", tag, and "]".
885        Ok(js_string!(js_str!("[object "), tag, js_str!("]")).into())
886    }
887
888    /// `Object.prototype.toLocaleString( [ reserved1 [ , reserved2 ] ] )`
889    ///
890    /// More information:
891    ///  - [ECMAScript reference][spec]
892    ///  - [MDN documentation][mdn]
893    ///
894    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.tolocalestring
895    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toLocaleString
896    #[allow(clippy::wrong_self_convention)]
897    pub fn to_locale_string(
898        this: &JsValue,
899        _: &[JsValue],
900        context: &mut Context,
901    ) -> JsResult<JsValue> {
902        // 1. Let O be the this value.
903        // 2. Return ? Invoke(O, "toString").
904        this.invoke(js_string!("toString"), &[], context)
905    }
906
907    /// `Object.prototype.hasOwnProperty( property )`
908    ///
909    /// The method returns a boolean indicating whether the object has the specified property
910    /// as its own property (as opposed to inheriting it).
911    ///
912    /// More information:
913    ///  - [ECMAScript reference][spec]
914    ///  - [MDN documentation][mdn]
915    ///
916    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.hasownproperty
917    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
918    pub fn has_own_property(
919        this: &JsValue,
920        args: &[JsValue],
921        context: &mut Context,
922    ) -> JsResult<JsValue> {
923        // 1. Let P be ? ToPropertyKey(V).
924        let key = args.get_or_undefined(0).to_property_key(context)?;
925
926        // 2. Let O be ? ToObject(this value).
927        let object = this.to_object(context)?;
928
929        // 3. Return ? HasOwnProperty(O, P).
930        Ok(object.has_own_property(key, context)?.into())
931    }
932
933    /// `Object.prototype.propertyIsEnumerable( property )`
934    ///
935    /// This method returns a Boolean indicating whether the specified property is
936    /// enumerable and is the object's own property.
937    ///
938    /// More information:
939    ///  - [ECMAScript reference][spec]
940    ///  - [MDN documentation][mdn]
941    ///
942    /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable
943    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/propertyIsEnumerable
944    pub fn property_is_enumerable(
945        this: &JsValue,
946        args: &[JsValue],
947        context: &mut Context,
948    ) -> JsResult<JsValue> {
949        let Some(key) = args.first() else {
950            return Ok(JsValue::new(false));
951        };
952
953        let key = key.to_property_key(context)?;
954
955        let own_prop = this
956            .to_object(context)?
957            .__get_own_property__(&key, &mut InternalMethodPropertyContext::new(context))?;
958
959        own_prop
960            .as_ref()
961            .and_then(PropertyDescriptor::enumerable)
962            .unwrap_or_default()
963            .conv::<JsValue>()
964            .pipe(Ok)
965    }
966
967    /// `Object.assign( target, ...sources )`
968    ///
969    /// This method copies all enumerable own properties from one or more
970    /// source objects to a target object. It returns the target object.
971    ///
972    /// More information:
973    ///  - [ECMAScript reference][spec]
974    ///  - [MDN documentation][mdn]
975    ///
976    /// [spec]: https://tc39.es/ecma262/#sec-object.assign
977    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
978    pub fn assign(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
979        // 1. Let to be ? ToObject(target).
980        let to = args.get_or_undefined(0).to_object(context)?;
981
982        // 2. If only one argument was passed, return to.
983        if args.len() == 1 {
984            return Ok(to.into());
985        }
986
987        // 3. For each element nextSource of sources, do
988        for source in &args[1..] {
989            // 3.a. If nextSource is neither undefined nor null, then
990            if !source.is_null_or_undefined() {
991                // 3.a.i. Let from be ! ToObject(nextSource).
992                let from = source
993                    .to_object(context)
994                    .expect("this ToObject call must not fail");
995                // 3.a.ii. Let keys be ? from.[[OwnPropertyKeys]]().
996                let keys =
997                    from.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
998                // 3.a.iii. For each element nextKey of keys, do
999                for key in keys {
1000                    // 3.a.iii.1. Let desc be ? from.[[GetOwnProperty]](nextKey).
1001
1002                    if let Some(desc) = from.__get_own_property__(
1003                        &key,
1004                        &mut InternalMethodPropertyContext::new(context),
1005                    )? {
1006                        // 3.a.iii.2. If desc is not undefined and desc.[[Enumerable]] is true, then
1007                        if desc.expect_enumerable() {
1008                            // 3.a.iii.2.a. Let propValue be ? Get(from, nextKey).
1009                            let property = from.get(key.clone(), context)?;
1010                            // 3.a.iii.2.b. Perform ? Set(to, nextKey, propValue, true).
1011                            to.set(key, property, true, context)?;
1012                        }
1013                    }
1014                }
1015            }
1016        }
1017
1018        // 4. Return to.
1019        Ok(to.into())
1020    }
1021
1022    /// `Object.keys( target )`
1023    ///
1024    /// This method returns an array of a given object's own enumerable
1025    /// property names, iterated in the same order that a normal loop would.
1026    ///
1027    /// More information:
1028    ///  - [ECMAScript reference][spec]
1029    ///  - [MDN documentation][mdn]
1030    ///
1031    /// [spec]: https://tc39.es/ecma262/#sec-object.keys
1032    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
1033    pub fn keys(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1034        // 1. Let obj be ? ToObject(target).
1035        let obj = args
1036            .first()
1037            .cloned()
1038            .unwrap_or_default()
1039            .to_object(context)?;
1040
1041        // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key).
1042        let name_list = obj.enumerable_own_property_names(PropertyNameKind::Key, context)?;
1043
1044        // 3. Return CreateArrayFromList(nameList).
1045        let result = Array::create_array_from_list(name_list, context);
1046
1047        Ok(result.into())
1048    }
1049
1050    /// `Object.values( target )`
1051    ///
1052    /// More information:
1053    ///  - [ECMAScript reference][spec]
1054    ///  - [MDN documentation][mdn]
1055    ///
1056    /// [spec]: https://tc39.es/ecma262/#sec-object.values
1057    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values
1058    pub fn values(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1059        // 1. Let obj be ? ToObject(target).
1060        let obj = args
1061            .first()
1062            .cloned()
1063            .unwrap_or_default()
1064            .to_object(context)?;
1065
1066        // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, value).
1067        let name_list = obj.enumerable_own_property_names(PropertyNameKind::Value, context)?;
1068
1069        // 3. Return CreateArrayFromList(nameList).
1070        let result = Array::create_array_from_list(name_list, context);
1071
1072        Ok(result.into())
1073    }
1074
1075    /// `Object.entries( target )`
1076    ///
1077    /// This method returns an array of a given object's own enumerable string-keyed property [key, value] pairs.
1078    /// This is the same as iterating with a for...in loop,
1079    /// except that a for...in loop enumerates properties in the prototype chain as well).
1080    ///
1081    /// More information:
1082    ///  - [ECMAScript reference][spec]
1083    ///  - [MDN documentation][mdn]
1084    ///
1085    /// [spec]: https://tc39.es/ecma262/#sec-object.entries
1086    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
1087    pub fn entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1088        // 1. Let obj be ? ToObject(target).
1089        let obj = args
1090            .first()
1091            .cloned()
1092            .unwrap_or_default()
1093            .to_object(context)?;
1094
1095        // 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key+value).
1096        let name_list =
1097            obj.enumerable_own_property_names(PropertyNameKind::KeyAndValue, context)?;
1098
1099        // 3. Return CreateArrayFromList(nameList).
1100        let result = Array::create_array_from_list(name_list, context);
1101
1102        Ok(result.into())
1103    }
1104
1105    /// `Object.seal( target )`
1106    ///
1107    /// More information:
1108    ///  - [ECMAScript reference][spec]
1109    ///  - [MDN documentation][mdn]
1110    ///
1111    /// [spec]: https://tc39.es/ecma262/#sec-object.seal
1112    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal
1113    pub fn seal(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1114        let o = args.get_or_undefined(0);
1115
1116        if let Some(o) = o.as_object() {
1117            // 2. Let status be ? SetIntegrityLevel(O, sealed).
1118            let status = o.set_integrity_level(IntegrityLevel::Sealed, context)?;
1119            // 3. If status is false, throw a TypeError exception.
1120            if !status {
1121                return Err(JsNativeError::typ()
1122                    .with_message("cannot seal object")
1123                    .into());
1124            }
1125        }
1126        // 1. If Type(O) is not Object, return O.
1127        // 4. Return O.
1128        Ok(o.clone())
1129    }
1130
1131    /// `Object.isSealed( target )`
1132    ///
1133    /// More information:
1134    ///  - [ECMAScript reference][spec]
1135    ///  - [MDN documentation][mdn]
1136    ///
1137    /// [spec]: https://tc39.es/ecma262/#sec-object.issealed
1138    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed
1139    pub fn is_sealed(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1140        let o = args.get_or_undefined(0);
1141
1142        // 1. If Type(O) is not Object, return true.
1143        // 2. Return ? TestIntegrityLevel(O, sealed).
1144        if let Some(o) = o.as_object() {
1145            Ok(o.test_integrity_level(IntegrityLevel::Sealed, context)?
1146                .into())
1147        } else {
1148            Ok(JsValue::new(true))
1149        }
1150    }
1151
1152    /// `Object.freeze( target )`
1153    ///
1154    /// More information:
1155    ///  - [ECMAScript reference][spec]
1156    ///  - [MDN documentation][mdn]
1157    ///
1158    /// [spec]: https://tc39.es/ecma262/#sec-object.freeze
1159    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
1160    pub fn freeze(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1161        let o = args.get_or_undefined(0);
1162
1163        if let Some(o) = o.as_object() {
1164            // 2. Let status be ? SetIntegrityLevel(O, frozen).
1165            let status = o.set_integrity_level(IntegrityLevel::Frozen, context)?;
1166            // 3. If status is false, throw a TypeError exception.
1167            if !status {
1168                return Err(JsNativeError::typ()
1169                    .with_message("cannot freeze object")
1170                    .into());
1171            }
1172        }
1173        // 1. If Type(O) is not Object, return O.
1174        // 4. Return O.
1175        Ok(o.clone())
1176    }
1177
1178    /// `Object.isFrozen( target )`
1179    ///
1180    /// More information:
1181    ///  - [ECMAScript reference][spec]
1182    ///  - [MDN documentation][mdn]
1183    ///
1184    /// [spec]: https://tc39.es/ecma262/#sec-object.isfrozen
1185    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen
1186    pub fn is_frozen(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1187        let o = args.get_or_undefined(0);
1188
1189        // 1. If Type(O) is not Object, return true.
1190        // 2. Return ? TestIntegrityLevel(O, frozen).
1191        if let Some(o) = o.as_object() {
1192            Ok(o.test_integrity_level(IntegrityLevel::Frozen, context)?
1193                .into())
1194        } else {
1195            Ok(JsValue::new(true))
1196        }
1197    }
1198
1199    /// `Object.preventExtensions( target )`
1200    ///
1201    /// More information:
1202    ///  - [ECMAScript reference][spec]
1203    ///  - [MDN documentation][mdn]
1204    ///
1205    /// [spec]: https://tc39.es/ecma262/#sec-object.preventextensions
1206    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions
1207    pub fn prevent_extensions(
1208        _: &JsValue,
1209        args: &[JsValue],
1210        context: &mut Context,
1211    ) -> JsResult<JsValue> {
1212        let o = args.get_or_undefined(0);
1213
1214        if let Some(o) = o.as_object() {
1215            // 2. Let status be ? O.[[PreventExtensions]]().
1216            let status =
1217                o.__prevent_extensions__(&mut InternalMethodPropertyContext::new(context))?;
1218            // 3. If status is false, throw a TypeError exception.
1219            if !status {
1220                return Err(JsNativeError::typ()
1221                    .with_message("cannot prevent extensions")
1222                    .into());
1223            }
1224        }
1225        // 1. If Type(O) is not Object, return O.
1226        // 4. Return O.
1227        Ok(o.clone())
1228    }
1229
1230    /// `Object.isExtensible( target )`
1231    ///
1232    /// More information:
1233    ///  - [ECMAScript reference][spec]
1234    ///  - [MDN documentation][mdn]
1235    ///
1236    /// [spec]: https://tc39.es/ecma262/#sec-object.isextensible
1237    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible
1238    pub fn is_extensible(
1239        _: &JsValue,
1240        args: &[JsValue],
1241        context: &mut Context,
1242    ) -> JsResult<JsValue> {
1243        let o = args.get_or_undefined(0);
1244        // 1. If Type(O) is not Object, return false.
1245        if let Some(o) = o.as_object() {
1246            // 2. Return ? IsExtensible(O).
1247            Ok(o.is_extensible(context)?.into())
1248        } else {
1249            Ok(JsValue::new(false))
1250        }
1251    }
1252
1253    /// `Object.getOwnPropertyNames( object )`
1254    ///
1255    /// More information:
1256    ///  - [ECMAScript reference][spec]
1257    ///  - [MDN documentation][mdn]
1258    ///
1259    /// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertynames
1260    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames
1261    pub fn get_own_property_names(
1262        _: &JsValue,
1263        args: &[JsValue],
1264        context: &mut Context,
1265    ) -> JsResult<JsValue> {
1266        // 1. Return ? GetOwnPropertyKeys(O, string).
1267        let o = args.get_or_undefined(0);
1268        get_own_property_keys(o, PropertyKeyType::String, context)
1269    }
1270
1271    /// `Object.getOwnPropertySymbols( object )`
1272    ///
1273    /// More information:
1274    ///  - [ECMAScript reference][spec]
1275    ///  - [MDN documentation][mdn]
1276    ///
1277    /// [spec]: https://tc39.es/ecma262/#sec-object.getownpropertysymbols
1278    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols
1279    pub fn get_own_property_symbols(
1280        _: &JsValue,
1281        args: &[JsValue],
1282        context: &mut Context,
1283    ) -> JsResult<JsValue> {
1284        // 1. Return ? GetOwnPropertyKeys(O, symbol).
1285        let o = args.get_or_undefined(0);
1286        get_own_property_keys(o, PropertyKeyType::Symbol, context)
1287    }
1288
1289    /// `Object.hasOwn( object, property )`
1290    ///
1291    /// More information:
1292    ///  - [ECMAScript reference][spec]
1293    ///  - [MDN documentation][mdn]
1294    ///
1295    /// [spec]: https://tc39.es/ecma262/#sec-object.hasown
1296    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn
1297    pub fn has_own(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1298        // 1. Let obj be ? ToObject(O).
1299        let obj = args.get_or_undefined(0).to_object(context)?;
1300
1301        // 2. Let key be ? ToPropertyKey(P).
1302        let key = args.get_or_undefined(1).to_property_key(context)?;
1303
1304        // 3. Return ? HasOwnProperty(obj, key).
1305        Ok(obj.has_own_property(key, context)?.into())
1306    }
1307
1308    /// `Object.fromEntries( iterable )`
1309    ///
1310    /// More information:
1311    ///  - [ECMAScript reference][spec]
1312    ///  - [MDN documentation][mdn]
1313    ///
1314    /// [spec]: https://tc39.es/ecma262/#sec-object.fromentries
1315    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries
1316    pub fn from_entries(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
1317        // 1. Perform ? RequireObjectCoercible(iterable).
1318        let iterable = args.get_or_undefined(0).require_object_coercible()?;
1319
1320        // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
1321        // 3. Assert: obj is an extensible ordinary object with no own properties.
1322        let obj = JsObject::with_object_proto(context.intrinsics());
1323
1324        // 4. Let closure be a new Abstract Closure with parameters (key, value) that captures
1325        // obj and performs the following steps when called:
1326        let closure = FunctionObjectBuilder::new(
1327            context.realm(),
1328            NativeFunction::from_copy_closure_with_captures(
1329                |_, args, obj, context| {
1330                    let key = args.get_or_undefined(0);
1331                    let value = args.get_or_undefined(1);
1332
1333                    // a. Let propertyKey be ? ToPropertyKey(key).
1334                    let property_key = key.to_property_key(context)?;
1335
1336                    // b. Perform ! CreateDataPropertyOrThrow(obj, propertyKey, value).
1337                    obj.create_data_property_or_throw(property_key, value.clone(), context)?;
1338
1339                    // c. Return undefined.
1340                    Ok(JsValue::undefined())
1341                },
1342                obj.clone(),
1343            ),
1344        );
1345
1346        // 5. Let adder be ! CreateBuiltinFunction(closure, 2, "", « »).
1347        let adder = closure.length(2).name("").build();
1348
1349        // 6. Return ? AddEntriesFromIterable(obj, iterable, adder).
1350        map::add_entries_from_iterable(&obj, iterable, &adder, context)
1351    }
1352
1353    /// [`Object.groupBy ( items, callbackfn )`][spec]
1354    ///
1355    /// [spec]: https://tc39.es/ecma262/#sec-object.groupby
1356    pub(crate) fn group_by(
1357        _: &JsValue,
1358        args: &[JsValue],
1359        context: &mut Context,
1360    ) -> JsResult<JsValue> {
1361        use std::hash::BuildHasherDefault;
1362
1363        use indexmap::IndexMap;
1364        use rustc_hash::FxHasher;
1365
1366        use crate::builtins::{Number, iterable::if_abrupt_close_iterator};
1367
1368        let items = args.get_or_undefined(0);
1369        let callback = args.get_or_undefined(1);
1370        // 1. Let groups be ? GroupBy(items, callbackfn, property).
1371
1372        // `GroupBy`
1373        // https://tc39.es/ecma262/#sec-groupby
1374        // inlined to change the key type.
1375
1376        // 1. Perform ? RequireObjectCoercible(items).
1377        items.require_object_coercible()?;
1378
1379        // 2. If IsCallable(callbackfn) is false, throw a TypeError exception.
1380        let callback = callback.as_callable().ok_or_else(|| {
1381            JsNativeError::typ().with_message("callback must be a callable object")
1382        })?;
1383
1384        // 3. Let groups be a new empty List.
1385        let mut groups: IndexMap<PropertyKey, Vec<JsValue>, BuildHasherDefault<FxHasher>> =
1386            IndexMap::default();
1387
1388        // 4. Let iteratorRecord be ? GetIterator(items, sync).
1389        let mut iterator = items.get_iterator(IteratorHint::Sync, context)?;
1390
1391        // 5. Let k be 0.
1392        let mut k = 0u64;
1393
1394        // 6. Repeat,
1395        loop {
1396            // a. If k ≥ 2^53 - 1, then
1397            if k >= Number::MAX_SAFE_INTEGER as u64 {
1398                // i. Let error be ThrowCompletion(a newly created TypeError object).
1399                let error = JsNativeError::typ()
1400                    .with_message("exceeded maximum safe integer")
1401                    .into();
1402
1403                // ii. Return ? IteratorClose(iteratorRecord, error).
1404                return iterator.close(Err(error), context);
1405            }
1406
1407            // b. Let next be ? IteratorStepValue(iteratorRecord).
1408            let Some(next) = iterator.step_value(context)? else {
1409                // c. If next is false, then
1410                // i. Return groups.
1411                break;
1412            };
1413
1414            // d. Let value be next.
1415            let value = next;
1416
1417            // e. Let key be Completion(Call(callbackfn, undefined, « value, 𝔽(k) »)).
1418            let key = callback.call(&JsValue::undefined(), &[value.clone(), k.into()], context);
1419
1420            // f. IfAbruptCloseIterator(key, iteratorRecord).
1421            let key = if_abrupt_close_iterator!(key, iterator, context);
1422
1423            // g. If keyCoercion is property, then
1424            //     i. Set key to Completion(ToPropertyKey(key)).
1425            let key = key.to_property_key(context);
1426
1427            //     ii. IfAbruptCloseIterator(key, iteratorRecord).
1428            let key = if_abrupt_close_iterator!(key, iterator, context);
1429
1430            // i. Perform AddValueToKeyedGroup(groups, key, value).
1431            groups.entry(key).or_default().push(value);
1432
1433            // j. Set k to k + 1.
1434            k += 1;
1435        }
1436
1437        // 2. Let obj be OrdinaryObjectCreate(null).
1438        let obj = JsObject::with_null_proto();
1439
1440        // 3. For each Record { [[Key]], [[Elements]] } g of groups, do
1441        for (key, elements) in groups {
1442            // a. Let elements be CreateArrayFromList(g.[[Elements]]).
1443            let elements = Array::create_array_from_list(elements, context);
1444
1445            // b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
1446            obj.create_data_property_or_throw(key, elements, context)
1447                .expect("cannot fail for a newly created object");
1448        }
1449
1450        // 4. Return obj.
1451        Ok(obj.into())
1452    }
1453}
1454
1455/// The abstract operation `ObjectDefineProperties`
1456///
1457/// More information:
1458///  - [ECMAScript reference][spec]
1459///
1460/// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties
1461fn object_define_properties(
1462    object: &JsObject,
1463    props: &JsValue,
1464    context: &mut Context,
1465) -> JsResult<()> {
1466    // 1. Assert: Type(O) is Object.
1467    // 2. Let props be ? ToObject(Properties).
1468    let props = &props.to_object(context)?;
1469
1470    // 3. Let keys be ? props.[[OwnPropertyKeys]]().
1471    let keys = props.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
1472
1473    // 4. Let descriptors be a new empty List.
1474    let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new();
1475
1476    // 5. For each element nextKey of keys, do
1477    for next_key in keys {
1478        // a. Let propDesc be ? props.[[GetOwnProperty]](nextKey).
1479        // b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then
1480
1481        if let Some(prop_desc) = props
1482            .__get_own_property__(&next_key, &mut InternalMethodPropertyContext::new(context))?
1483            && prop_desc.expect_enumerable()
1484        {
1485            // i. Let descObj be ? Get(props, nextKey).
1486            let desc_obj = props.get(next_key.clone(), context)?;
1487
1488            // ii. Let desc be ? ToPropertyDescriptor(descObj).
1489            let desc = desc_obj.to_property_descriptor(context)?;
1490
1491            // iii. Append the pair (a two element List) consisting of nextKey and desc to the end of descriptors.
1492            descriptors.push((next_key, desc));
1493        }
1494    }
1495
1496    // 6. For each element pair of descriptors, do
1497    // a. Let P be the first element of pair.
1498    // b. Let desc be the second element of pair.
1499    for (p, d) in descriptors {
1500        // c. Perform ? DefinePropertyOrThrow(O, P, desc).
1501        object.define_property_or_throw(p, d, context)?;
1502    }
1503
1504    // 7. Return O.
1505    Ok(())
1506}
1507
1508/// Type enum used in the abstract operation `GetOwnPropertyKeys`.
1509#[derive(Debug, Copy, Clone)]
1510enum PropertyKeyType {
1511    String,
1512    Symbol,
1513}
1514
1515/// The abstract operation `GetOwnPropertyKeys`.
1516///
1517/// More information:
1518///  - [ECMAScript reference][spec]
1519///
1520/// [spec]: https://tc39.es/ecma262/#sec-getownpropertykeys
1521fn get_own_property_keys(
1522    o: &JsValue,
1523    r#type: PropertyKeyType,
1524    context: &mut Context,
1525) -> JsResult<JsValue> {
1526    // 1. Let obj be ? ToObject(o).
1527    let obj = o.to_object(context)?;
1528
1529    // 2. Let keys be ? obj.[[OwnPropertyKeys]]().
1530    let keys = obj.__own_property_keys__(&mut InternalMethodPropertyContext::new(context))?;
1531
1532    // 3. Let nameList be a new empty List.
1533    // 4. For each element nextKey of keys, do
1534    let name_list = keys.iter().filter_map(|next_key| {
1535        // a. If Type(nextKey) is Symbol and type is symbol or Type(nextKey) is String and type is string, then
1536        // i. Append nextKey as the last element of nameList.
1537        match (r#type, &next_key) {
1538            (PropertyKeyType::String, PropertyKey::String(_))
1539            | (PropertyKeyType::Symbol, PropertyKey::Symbol(_)) => Some(next_key.into()),
1540            (PropertyKeyType::String, PropertyKey::Index(index)) => {
1541                Some(js_string!(index.get()).into())
1542            }
1543            _ => None,
1544        }
1545    });
1546
1547    // 5. Return CreateArrayFromList(nameList).
1548    Ok(Array::create_array_from_list(name_list, context).into())
1549}