diff --git a/boa_engine/src/builtins/error/eval.rs b/boa_engine/src/builtins/error/eval.rs index fabd901de4e..046f5cc49b8 100644 --- a/boa_engine/src/builtins/error/eval.rs +++ b/boa_engine/src/builtins/error/eval.rs @@ -12,7 +12,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError use crate::{ - builtins::BuiltIn, + builtins::{BuiltIn, JsArgs}, context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, @@ -22,6 +22,8 @@ use crate::{ }; use boa_profiler::Profiler; +use super::Error; + /// JavaScript `EvalError` impleentation. #[derive(Debug, Clone, Copy)] pub(crate) struct EvalError; @@ -64,14 +66,29 @@ impl EvalError { args: &[JsValue], context: &mut Context, ) -> JsResult { - let prototype = - get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = JsObject::from_proto_and_data(prototype, ObjectData::error()); - if let Some(message) = args.get(0) { - if !message.is_undefined() { - obj.set("message", message.to_string(context)?, false, context)?; - } + // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). + let prototype = get_prototype_from_constructor( + new_target, + StandardObjects::eval_error_object, + context, + )?; + let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + + // 3. If message is not undefined, then + let message = args.get_or_undefined(0); + if !message.is_undefined() { + // a. Let msg be ? ToString(message). + let msg = message.to_string(context)?; + + // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). + o.create_non_enumerable_data_property_or_throw("message", msg, context); } - Ok(obj.into()) + + // 4. Perform ? InstallErrorCause(O, options). + Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + + // 5. Return O. + Ok(o.into()) } } diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index ca9e7db09cf..eb9a79f2676 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -38,6 +38,8 @@ pub(crate) use self::reference::ReferenceError; pub(crate) use self::syntax::SyntaxError; pub(crate) use self::uri::UriError; +use super::JsArgs; + /// Built-in `Error` object. #[derive(Debug, Clone, Copy)] pub(crate) struct Error; @@ -73,7 +75,27 @@ impl Error { /// The amount of arguments this function object takes. pub(crate) const LENGTH: usize = 1; - /// `Error( message )` + pub(crate) fn install_error_cause( + o: &JsObject, + options: &JsValue, + context: &mut Context, + ) -> JsResult<()> { + // 1. If Type(options) is Object and ? HasProperty(options, "cause") is true, then + if let Some(options) = options.as_object() { + if options.has_property("cause", context)? { + // a. Let cause be ? Get(options, "cause"). + let cause = options.get("cause", context)?; + + // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "cause", cause). + o.create_non_enumerable_data_property_or_throw("cause", cause, context); + } + } + + // 2. Return unused. + Ok(()) + } + + /// `Error( message [ , options ] )` /// /// Create a new error object. pub(crate) fn constructor( @@ -81,15 +103,28 @@ impl Error { args: &[JsValue], context: &mut Context, ) -> JsResult { + // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »). let prototype = get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = JsObject::from_proto_and_data(prototype, ObjectData::error()); - if let Some(message) = args.get(0) { - if !message.is_undefined() { - obj.set("message", message.to_string(context)?, false, context)?; - } + let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + + // 3. If message is not undefined, then + let message = args.get_or_undefined(0); + if !message.is_undefined() { + // a. Let msg be ? ToString(message). + let msg = message.to_string(context)?; + + // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). + o.create_non_enumerable_data_property_or_throw("message", msg, context); } - Ok(obj.into()) + + // 4. Perform ? InstallErrorCause(O, options). + Self::install_error_cause(&o, args.get_or_undefined(1), context)?; + + // 5. Return O. + Ok(o.into()) } /// `Error.prototype.toString()` diff --git a/boa_engine/src/builtins/error/range.rs b/boa_engine/src/builtins/error/range.rs index 9611d503734..94eb245a571 100644 --- a/boa_engine/src/builtins/error/range.rs +++ b/boa_engine/src/builtins/error/range.rs @@ -10,7 +10,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError use crate::{ - builtins::BuiltIn, + builtins::{BuiltIn, JsArgs}, context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, @@ -20,6 +20,8 @@ use crate::{ }; use boa_profiler::Profiler; +use super::Error; + /// JavaScript `RangeError` implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct RangeError; @@ -62,14 +64,29 @@ impl RangeError { args: &[JsValue], context: &mut Context, ) -> JsResult { - let prototype = - get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = JsObject::from_proto_and_data(prototype, ObjectData::error()); - if let Some(message) = args.get(0) { - if !message.is_undefined() { - obj.set("message", message.to_string(context)?, false, context)?; - } + // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). + let prototype = get_prototype_from_constructor( + new_target, + StandardObjects::range_error_object, + context, + )?; + let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + + // 3. If message is not undefined, then + let message = args.get_or_undefined(0); + if !message.is_undefined() { + // a. Let msg be ? ToString(message). + let msg = message.to_string(context)?; + + // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). + o.create_non_enumerable_data_property_or_throw("message", msg, context); } - Ok(obj.into()) + + // 4. Perform ? InstallErrorCause(O, options). + Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + + // 5. Return O. + Ok(o.into()) } } diff --git a/boa_engine/src/builtins/error/reference.rs b/boa_engine/src/builtins/error/reference.rs index bacb9ab58ac..b8d3ece6a6e 100644 --- a/boa_engine/src/builtins/error/reference.rs +++ b/boa_engine/src/builtins/error/reference.rs @@ -10,7 +10,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError use crate::{ - builtins::BuiltIn, + builtins::{BuiltIn, JsArgs}, context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, @@ -20,6 +20,8 @@ use crate::{ }; use boa_profiler::Profiler; +use super::Error; + #[derive(Debug, Clone, Copy)] pub(crate) struct ReferenceError; @@ -61,14 +63,29 @@ impl ReferenceError { args: &[JsValue], context: &mut Context, ) -> JsResult { - let prototype = - get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = JsObject::from_proto_and_data(prototype, ObjectData::error()); - if let Some(message) = args.get(0) { - if !message.is_undefined() { - obj.set("message", message.to_string(context)?, false, context)?; - } + // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). + let prototype = get_prototype_from_constructor( + new_target, + StandardObjects::reference_error_object, + context, + )?; + let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + + // 3. If message is not undefined, then + let message = args.get_or_undefined(0); + if !message.is_undefined() { + // a. Let msg be ? ToString(message). + let msg = message.to_string(context)?; + + // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). + o.create_non_enumerable_data_property_or_throw("message", msg, context); } - Ok(obj.into()) + + // 4. Perform ? InstallErrorCause(O, options). + Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + + // 5. Return O. + Ok(o.into()) } } diff --git a/boa_engine/src/builtins/error/syntax.rs b/boa_engine/src/builtins/error/syntax.rs index bc4a1824b9c..f04695be5cb 100644 --- a/boa_engine/src/builtins/error/syntax.rs +++ b/boa_engine/src/builtins/error/syntax.rs @@ -12,7 +12,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError use crate::{ - builtins::BuiltIn, + builtins::{BuiltIn, JsArgs}, context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, @@ -22,6 +22,8 @@ use crate::{ }; use boa_profiler::Profiler; +use super::Error; + /// JavaScript `SyntaxError` impleentation. #[derive(Debug, Clone, Copy)] pub(crate) struct SyntaxError; @@ -64,14 +66,29 @@ impl SyntaxError { args: &[JsValue], context: &mut Context, ) -> JsResult { - let prototype = - get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = JsObject::from_proto_and_data(prototype, ObjectData::error()); - if let Some(message) = args.get(0) { - if !message.is_undefined() { - obj.set("message", message.to_string(context)?, false, context)?; - } + // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). + let prototype = get_prototype_from_constructor( + new_target, + StandardObjects::syntax_error_object, + context, + )?; + let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + + // 3. If message is not undefined, then + let message = args.get_or_undefined(0); + if !message.is_undefined() { + // a. Let msg be ? ToString(message). + let msg = message.to_string(context)?; + + // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). + o.create_non_enumerable_data_property_or_throw("message", msg, context); } - Ok(obj.into()) + + // 4. Perform ? InstallErrorCause(O, options). + Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + + // 5. Return O. + Ok(o.into()) } } diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 4064aa384bf..59fdd9e98f2 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -16,7 +16,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError use crate::{ - builtins::BuiltIn, + builtins::{BuiltIn, JsArgs}, context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, @@ -26,6 +26,8 @@ use crate::{ }; use boa_profiler::Profiler; +use super::Error; + /// JavaScript `TypeError` implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct TypeError; @@ -68,14 +70,29 @@ impl TypeError { args: &[JsValue], context: &mut Context, ) -> JsResult { - let prototype = - get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = JsObject::from_proto_and_data(prototype, ObjectData::error()); - if let Some(message) = args.get(0) { - if !message.is_undefined() { - obj.set("message", message.to_string(context)?, false, context)?; - } + // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). + let prototype = get_prototype_from_constructor( + new_target, + StandardObjects::type_error_object, + context, + )?; + let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + + // 3. If message is not undefined, then + let message = args.get_or_undefined(0); + if !message.is_undefined() { + // a. Let msg be ? ToString(message). + let msg = message.to_string(context)?; + + // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). + o.create_non_enumerable_data_property_or_throw("message", msg, context); } - Ok(obj.into()) + + // 4. Perform ? InstallErrorCause(O, options). + Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + + // 5. Return O. + Ok(o.into()) } } diff --git a/boa_engine/src/builtins/error/uri.rs b/boa_engine/src/builtins/error/uri.rs index e16628f9d7f..c45d6928613 100644 --- a/boa_engine/src/builtins/error/uri.rs +++ b/boa_engine/src/builtins/error/uri.rs @@ -11,7 +11,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError use crate::{ - builtins::BuiltIn, + builtins::{BuiltIn, JsArgs}, context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, @@ -21,6 +21,8 @@ use crate::{ }; use boa_profiler::Profiler; +use super::Error; + /// JavaScript `URIError` impleentation. #[derive(Debug, Clone, Copy)] pub(crate) struct UriError; @@ -63,14 +65,26 @@ impl UriError { args: &[JsValue], context: &mut Context, ) -> JsResult { + // 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. + // 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »). let prototype = - get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = JsObject::from_proto_and_data(prototype, ObjectData::error()); - if let Some(message) = args.get(0) { - if !message.is_undefined() { - obj.set("message", message.to_string(context)?, false, context)?; - } + get_prototype_from_constructor(new_target, StandardObjects::uri_error_object, context)?; + let o = JsObject::from_proto_and_data(prototype, ObjectData::error()); + + // 3. If message is not undefined, then + let message = args.get_or_undefined(0); + if !message.is_undefined() { + // a. Let msg be ? ToString(message). + let msg = message.to_string(context)?; + + // b. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg). + o.create_non_enumerable_data_property_or_throw("message", msg, context); } - Ok(obj.into()) + + // 4. Perform ? InstallErrorCause(O, options). + Error::install_error_cause(&o, args.get_or_undefined(1), context)?; + + // 5. Return O. + Ok(o.into()) } } diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index bec8ff14525..597df4d14c0 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -2,7 +2,7 @@ use crate::{ builtins::Array, context::{StandardConstructor, StandardObjects}, object::JsObject, - property::{PropertyDescriptor, PropertyKey, PropertyNameKind}, + property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, value::Type, Context, JsResult, JsValue, @@ -156,6 +156,43 @@ impl JsObject { Ok(success) } + /// Create non-enumerable data property or throw + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-createnonenumerabledatapropertyinfallibly + pub(crate) fn create_non_enumerable_data_property_or_throw( + &self, + key: K, + value: V, + context: &mut Context, + ) where + K: Into, + V: Into, + { + // 1. Assert: O is an ordinary, extensible object with no non-configurable properties. + + // 2. Let newDesc be the PropertyDescriptor { + // [[Value]]: V, + // [[Writable]]: true, + // [[Enumerable]]: false, + // [[Configurable]]: true + // }. + let new_desc = PropertyDescriptorBuilder::new() + .value(value) + .writable(true) + .enumerable(false) + .configurable(true) + .build(); + + // 3. Perform ! DefinePropertyOrThrow(O, P, newDesc). + self.define_property_or_throw(key, new_desc, context) + .expect("should not fail according to spec"); + + // 4. Return unused. + } + /// Define property or throw. /// /// More information: