diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 1bcc922354..8fb623e002 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -230,7 +230,6 @@ impl Array { array.borrow_mut().data = ObjectData::array(); // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). - crate::object::internal_methods::ordinary_define_own_property( &array, "length".into(), @@ -274,7 +273,7 @@ impl Array { } /// Creates a new `Array` instance. - pub(crate) fn new_array(context: &Context) -> JsValue { + pub(crate) fn new_array(context: &mut Context) -> JsValue { let array = JsValue::new_object(context); array.set_data(ObjectData::array()); array diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 4f261c70b1..571c29b782 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -16,8 +16,8 @@ use crate::{ builtins::BuiltIn, object::{ - ConstructorBuilder, JsObject, Object as BuiltinObject, ObjectData, ObjectInitializer, - ObjectKind, PROTOTYPE, + ConstructorBuilder, IntegrityLevel, JsObject, Object as BuiltinObject, ObjectData, + ObjectInitializer, ObjectKind, PROTOTYPE, }, property::{Attribute, DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, @@ -68,6 +68,10 @@ impl BuiltIn for Object { .static_method(Self::keys, "keys", 1) .static_method(Self::values, "values", 1) .static_method(Self::entries, "entries", 1) + .static_method(Self::seal, "seal", 1) + .static_method(Self::is_sealed, "isSealed", 1) + .static_method(Self::freeze, "freeze", 1) + .static_method(Self::is_frozen, "isFrozen", 1) .static_method( Self::get_own_property_descriptor, "getOwnPropertyDescriptor", @@ -687,6 +691,96 @@ impl Object { Ok(result.into()) } + + /// `Object.seal( target )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object.seal + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal + pub fn seal(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let o = args.get(0).cloned().unwrap_or_default(); + + if let Some(o) = o.as_object() { + // 2. Let status be ? SetIntegrityLevel(O, sealed). + let status = o.set_integrity_level(IntegrityLevel::Sealed, context)?; + // 3. If status is false, throw a TypeError exception. + if !status { + return context.throw_type_error("cannot seal object"); + } + } + // 1. If Type(O) is not Object, return O. + // 4. Return O. + Ok(o) + } + + /// `Object.isSealed( target )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object.issealed + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed + pub fn is_sealed(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let o = args.get(0).cloned().unwrap_or_default(); + + // 1. If Type(O) is not Object, return true. + // 2. Return ? TestIntegrityLevel(O, sealed). + if let Some(o) = o.as_object() { + Ok(o.test_integrity_level(IntegrityLevel::Sealed, context)? + .into()) + } else { + Ok(JsValue::new(true)) + } + } + + /// `Object.freeze( target )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object.freeze + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze + pub fn freeze(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let o = args.get(0).cloned().unwrap_or_default(); + + if let Some(o) = o.as_object() { + // 2. Let status be ? SetIntegrityLevel(O, frozen). + let status = o.set_integrity_level(IntegrityLevel::Frozen, context)?; + // 3. If status is false, throw a TypeError exception. + if !status { + return context.throw_type_error("cannot freeze object"); + } + } + // 1. If Type(O) is not Object, return O. + // 4. Return O. + Ok(o) + } + + /// `Object.isFrozen( target )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-object.isfrozen + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen + pub fn is_frozen(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + let o = args.get(0).cloned().unwrap_or_default(); + + // 1. If Type(O) is not Object, return true. + // 2. Return ? TestIntegrityLevel(O, frozen). + if let Some(o) = o.as_object() { + Ok(o.test_integrity_level(IntegrityLevel::Frozen, context)? + .into()) + } else { + Ok(JsValue::new(true)) + } + } } /// The abstract operation ObjectDefineProperties diff --git a/boa/src/builtins/reflect/mod.rs b/boa/src/builtins/reflect/mod.rs index 7748107249..97004609e5 100644 --- a/boa/src/builtins/reflect/mod.rs +++ b/boa/src/builtins/reflect/mod.rs @@ -330,7 +330,7 @@ impl Reflect { args: &[JsValue], context: &mut Context, ) -> JsResult { - let mut target = args + let target = args .get(0) .and_then(|v| v.as_object()) .ok_or_else(|| context.construct_type_error("target must be an object"))?; diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index 1fbc0e9d38..6bbdadb642 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -51,7 +51,7 @@ enum FunctionBody { } impl JsObject { - /// Create a new `JsObject` from a `Object`. + /// Create a new `GcObject` from a `Object`. #[inline] pub fn new(object: Object) -> Self { Self(Gc::new(GcCell::new(object))) diff --git a/boa/src/object/internal_methods/mod.rs b/boa/src/object/internal_methods/mod.rs index 220e031f19..5ce33579d6 100644 --- a/boa/src/object/internal_methods/mod.rs +++ b/boa/src/object/internal_methods/mod.rs @@ -72,7 +72,7 @@ impl JsObject { /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions #[inline] - pub(crate) fn __prevent_extensions__(&mut self, context: &mut Context) -> JsResult { + pub(crate) fn __prevent_extensions__(&self, context: &mut Context) -> JsResult { let func = self.borrow().data.internal_methods.__prevent_extensions__; func(self, context) } diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index c7760c5c6a..e0f9286821 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -35,6 +35,7 @@ mod property_map; use crate::builtins::object::for_in_iterator::ForInIterator; pub use gcobject::{JsObject, RecursionLimiter, Ref, RefMut}; use internal_methods::InternalObjectMethods; +pub use operations::IntegrityLevel; pub use property_map::*; use self::internal_methods::{ diff --git a/boa/src/object/operations.rs b/boa/src/object/operations.rs index 00a1dc868d..d4938419ee 100644 --- a/boa/src/object/operations.rs +++ b/boa/src/object/operations.rs @@ -1,14 +1,58 @@ use crate::{ builtins::Array, + object::JsObject, property::{PropertyDescriptor, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, value::Type, Context, JsResult, JsValue, }; -use super::JsObject; +/// Object integrity level. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IntegrityLevel { + /// Sealed object integrity level. + /// + /// Preventing new properties from being added to it and marking all existing + /// properties as non-configurable. Values of present properties can still be + /// changed as long as they are writable. + Sealed, + + /// Frozen object integrity level + /// + /// A frozen object can no longer be changed; freezing an object prevents new + /// properties from being added to it, existing properties from being removed, + /// prevents changing the enumerability, configurability, or writability of + /// existing properties, and prevents the values of existing properties from + /// being changed. In addition, freezing an object also prevents its prototype + /// from being changed. + Frozen, +} + +impl IntegrityLevel { + /// Returns `true` if the integrity level is sealed. + pub fn is_sealed(&self) -> bool { + matches!(self, Self::Sealed) + } + + /// Returns `true` if the integrity level is frozen. + pub fn is_frozen(&self) -> bool { + matches!(self, Self::Frozen) + } +} impl JsObject { + /// Cehck if object is extensible. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-isextensible-o + #[inline] + pub fn is_extensible(&self, context: &mut Context) -> JsResult { + // 1. Return ? O.[[IsExtensible]](). + self.__is_extensible__(context) + } + /// Get property from object or throw. /// /// More information: @@ -16,7 +60,7 @@ impl JsObject { /// /// [spec]: https://tc39.es/ecma262/#sec-get-o-p #[inline] - pub(crate) fn get(&self, key: K, context: &mut Context) -> JsResult + pub fn get(&self, key: K, context: &mut Context) -> JsResult where K: Into, { @@ -33,13 +77,7 @@ impl JsObject { /// /// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw #[inline] - pub(crate) fn set( - &self, - key: K, - value: V, - throw: bool, - context: &mut Context, - ) -> JsResult + pub fn set(&self, key: K, value: V, throw: bool, context: &mut Context) -> JsResult where K: Into, V: Into, @@ -66,7 +104,7 @@ impl JsObject { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow - pub(crate) fn create_data_property( + pub fn create_data_property( &self, key: K, value: V, @@ -96,7 +134,7 @@ impl JsObject { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow - pub(crate) fn create_data_property_or_throw( + pub fn create_data_property_or_throw( &self, key: K, value: V, @@ -126,7 +164,7 @@ impl JsObject { /// /// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow #[inline] - pub(crate) fn define_property_or_throw( + pub fn define_property_or_throw( &self, key: K, desc: P, @@ -156,11 +194,7 @@ impl JsObject { /// /// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow #[inline] - pub(crate) fn delete_property_or_throw( - &self, - key: K, - context: &mut Context, - ) -> JsResult + pub fn delete_property_or_throw(&self, key: K, context: &mut Context) -> JsResult where K: Into, { @@ -212,9 +246,8 @@ impl JsObject { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-hasproperty - // NOTE: for now context is not used but it will in the future. #[inline] - pub(crate) fn has_property(&self, key: K, context: &mut Context) -> JsResult + pub fn has_property(&self, key: K, context: &mut Context) -> JsResult where K: Into, { @@ -231,7 +264,7 @@ impl JsObject { /// /// [spec]: https://tc39.es/ecma262/#sec-hasownproperty #[inline] - pub(crate) fn has_own_property(&self, key: K, context: &mut Context) -> JsResult + pub fn has_own_property(&self, key: K, context: &mut Context) -> JsResult where K: Into, { @@ -254,7 +287,7 @@ impl JsObject { // #[track_caller] #[inline] - pub(crate) fn call( + pub fn call( &self, this: &JsValue, args: &[JsValue], @@ -271,7 +304,7 @@ impl JsObject { // #[track_caller] #[inline] - pub(crate) fn construct( + pub fn construct( &self, args: &[JsValue], new_target: &JsValue, @@ -280,10 +313,127 @@ impl JsObject { self.call_construct(new_target, args, context, true) } - // todo: SetIntegrityLevel + /// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen]. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-setintegritylevel + #[inline] + pub fn set_integrity_level( + &self, + level: IntegrityLevel, + context: &mut Context, + ) -> JsResult { + // 1. Assert: Type(O) is Object. + // 2. Assert: level is either sealed or frozen. + + // 3. Let status be ? O.[[PreventExtensions]](). + let status = self.__prevent_extensions__(context)?; + // 4. If status is false, return false. + if !status { + return Ok(false); + } + + // 5. Let keys be ? O.[[OwnPropertyKeys]](). + let keys = self.__own_property_keys__(context)?; + + match level { + // 6. If level is sealed, then + IntegrityLevel::Sealed => { + // a. For each element k of keys, do + for k in keys { + // i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor { [[Configurable]]: false }). + self.define_property_or_throw( + k, + PropertyDescriptor::builder().configurable(false).build(), + context, + )?; + } + } + // 7. Else, + // a. Assert: level is frozen. + IntegrityLevel::Frozen => { + // b. For each element k of keys, do + for k in keys { + // i. Let currentDesc be ? O.[[GetOwnProperty]](k). + let current_desc = self.__get_own_property__(&k, context)?; + // ii. If currentDesc is not undefined, then + if let Some(current_desc) = current_desc { + // 1. If IsAccessorDescriptor(currentDesc) is true, then + let desc = if current_desc.is_accessor_descriptor() { + // a. Let desc be the PropertyDescriptor { [[Configurable]]: false }. + PropertyDescriptor::builder().configurable(false).build() + // 2. Else, + } else { + // a. Let desc be the PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }. + PropertyDescriptor::builder() + .configurable(false) + .writable(false) + .build() + }; + // 3. Perform ? DefinePropertyOrThrow(O, k, desc). + self.define_property_or_throw(k, desc, context)?; + } + } + } + } - // todo: TestIntegrityLevel + // 8. Return true. + Ok(true) + } + /// Check if the object is [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen]. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-testintegritylevel + #[inline] + pub fn test_integrity_level( + &self, + level: IntegrityLevel, + context: &mut Context, + ) -> JsResult { + // 1. Assert: Type(O) is Object. + // 2. Assert: level is either sealed or frozen. + + // 3. Let extensible be ? IsExtensible(O). + let extensible = self.is_extensible(context)?; + + // 4. If extensible is true, return false. + if extensible { + return Ok(false); + } + + // 5. NOTE: If the object is extensible, none of its properties are examined. + // 6. Let keys be ? O.[[OwnPropertyKeys]](). + let keys = self.__own_property_keys__(context)?; + + // 7. For each element k of keys, do + for k in keys { + // a. Let currentDesc be ? O.[[GetOwnProperty]](k). + let current_desc = self.__get_own_property__(&k, context)?; + // b. If currentDesc is not undefined, then + if let Some(current_desc) = current_desc { + // i. If currentDesc.[[Configurable]] is true, return false. + if current_desc.expect_configurable() { + return Ok(false); + } + // ii. If level is frozen and IsDataDescriptor(currentDesc) is true, then + if level.is_frozen() && current_desc.is_data_descriptor() { + // 1. If currentDesc.[[Writable]] is true, return false. + if current_desc.expect_writable() { + return Ok(false); + } + } + } + } + // 8. Return true. + Ok(true) + } + + #[inline] pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult { // 1. Assert: Type(obj) is Object. // 2. Return ℝ(? ToLength(? Get(obj, "length"))).