Skip to main content

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