diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index da0274cd51..b26331384a 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -21,7 +21,7 @@ use crate::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, IntegrityLevel, JsObject, ObjectData, ObjectKind, }, - property::{PropertyDescriptor, PropertyKey, PropertyNameKind}, + property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, value::JsValue, Context, JsResult, JsString, @@ -43,6 +43,14 @@ impl BuiltIn for Object { fn init(context: &mut Context) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); + let legacy_proto_getter = FunctionBuilder::native(context, Self::legacy_proto_getter) + .name("get __proto__") + .build(); + + let legacy_setter_proto = FunctionBuilder::native(context, Self::legacy_proto_setter) + .name("set __proto__") + .build(); + ConstructorBuilder::with_standard_constructor( context, Self::constructor, @@ -51,6 +59,12 @@ impl BuiltIn for Object { .name(Self::NAME) .length(Self::LENGTH) .inherit(None) + .accessor( + "__proto__", + Some(legacy_proto_getter), + Some(legacy_setter_proto), + Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) .method(Self::has_own_property, "hasOwnProperty", 1) .method(Self::property_is_enumerable, "propertyIsEnumerable", 1) .method(Self::to_string, "toString", 0) @@ -117,6 +131,75 @@ impl Object { Ok(context.construct_object().into()) } + /// `get Object.prototype.__proto__` + /// + /// The `__proto__` getter function exposes the value of the + /// internal `[[Prototype]]` of an object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-object.prototype.__proto__ + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto + pub fn legacy_proto_getter( + this: &JsValue, + _: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let O be ? ToObject(this value). + let obj = this.to_object(context)?; + + // 2. Return ? O.[[GetPrototypeOf]](). + let proto = obj.__get_prototype_of__(context)?; + + Ok(proto.map_or(JsValue::Null, JsValue::new)) + } + + /// `set Object.prototype.__proto__` + /// + /// The `__proto__` setter allows the `[[Prototype]]` of + /// an object to be mutated. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-set-object.prototype.__proto__ + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto + pub fn legacy_proto_setter( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let O be ? RequireObjectCoercible(this value). + let this = this.require_object_coercible(context)?; + + // 2. If Type(proto) is neither Object nor Null, return undefined. + let proto = match args.get_or_undefined(0) { + JsValue::Object(proto) => Some(proto.clone()), + JsValue::Null => None, + _ => return Ok(JsValue::undefined()), + }; + + // 3. If Type(O) is not Object, return undefined. + let object = match this { + JsValue::Object(object) => object, + _ => return Ok(JsValue::undefined()), + }; + + // 4. Let status be ? O.[[SetPrototypeOf]](proto). + let status = object.__set_prototype_of__(proto, context)?; + + // 5. If status is false, throw a TypeError exception. + if !status { + return context.throw_type_error("__proto__ called on null or undefined"); + } + + // 6. Return undefined. + Ok(JsValue::undefined()) + } + /// `Object.prototype.__defineGetter__(prop, func)` /// /// Binds an object's property to a function to be called when that property is looked up.