Browse Source

Implement `Object.seal/isSealed/freeze/isFrozen` (#1511)

* Implement `Object.seal/isSealed/freeze/isFrozen`

* Doc fix
pull/1509/head
Halid Odat 3 years ago committed by GitHub
parent
commit
383f648313
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      boa/src/builtins/array/mod.rs
  2. 98
      boa/src/builtins/object/mod.rs
  3. 2
      boa/src/builtins/reflect/mod.rs
  4. 2
      boa/src/object/gcobject.rs
  5. 2
      boa/src/object/internal_methods/mod.rs
  6. 1
      boa/src/object/mod.rs
  7. 198
      boa/src/object/operations.rs

3
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

98
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<JsValue> {
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<JsValue> {
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<JsValue> {
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<JsValue> {
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

2
boa/src/builtins/reflect/mod.rs

@ -330,7 +330,7 @@ impl Reflect {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
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"))?;

2
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)))

2
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<bool> {
pub(crate) fn __prevent_extensions__(&self, context: &mut Context) -> JsResult<bool> {
let func = self.borrow().data.internal_methods.__prevent_extensions__;
func(self, context)
}

1
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::{

198
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<bool> {
// 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<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue>
pub fn get<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue>
where
K: Into<PropertyKey>,
{
@ -33,13 +77,7 @@ impl JsObject {
///
/// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
#[inline]
pub(crate) fn set<K, V>(
&self,
key: K,
value: V,
throw: bool,
context: &mut Context,
) -> JsResult<bool>
pub fn set<K, V>(&self, key: K, value: V, throw: bool, context: &mut Context) -> JsResult<bool>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
@ -66,7 +104,7 @@ impl JsObject {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
pub(crate) fn create_data_property<K, V>(
pub fn create_data_property<K, V>(
&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<K, V>(
pub fn create_data_property_or_throw<K, V>(
&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<K, P>(
pub fn define_property_or_throw<K, P>(
&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<K>(
&self,
key: K,
context: &mut Context,
) -> JsResult<bool>
pub fn delete_property_or_throw<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
where
K: Into<PropertyKey>,
{
@ -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<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
pub fn has_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
where
K: Into<PropertyKey>,
{
@ -231,7 +264,7 @@ impl JsObject {
///
/// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
#[inline]
pub(crate) fn has_own_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
pub fn has_own_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
where
K: Into<PropertyKey>,
{
@ -254,7 +287,7 @@ impl JsObject {
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
#[track_caller]
#[inline]
pub(crate) fn call(
pub fn call(
&self,
this: &JsValue,
args: &[JsValue],
@ -271,7 +304,7 @@ impl JsObject {
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
#[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<bool> {
// 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<bool> {
// 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<usize> {
// 1. Assert: Type(obj) is Object.
// 2. Return ℝ(? ToLength(? Get(obj, "length"))).

Loading…
Cancel
Save