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}