boa/builtins/reflect/
mod.rs

1//! This module implements the global `Reflect` object.
2//!
3//! The `Reflect` global object is a built-in object that provides methods for interceptable
4//! JavaScript operations.
5//!
6//! More information:
7//!  - [ECMAScript reference][spec]
8//!  - [MDN documentation][mdn]
9//!
10//! [spec]: https://tc39.es/ecma262/#sec-reflect-object
11//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
12
13use crate::{
14    builtins::{self, BuiltIn},
15    object::ObjectInitializer,
16    property::Attribute,
17    symbol::WellKnownSymbols,
18    BoaProfiler, Context, JsResult, JsValue,
19};
20
21use super::{Array, JsArgs};
22
23#[cfg(test)]
24mod tests;
25
26/// Javascript `Reflect` object.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub(crate) struct Reflect;
29
30impl BuiltIn for Reflect {
31    const NAME: &'static str = "Reflect";
32
33    fn attribute() -> Attribute {
34        Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE
35    }
36
37    fn init(context: &mut Context) -> (&'static str, JsValue, Attribute) {
38        let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
39
40        let to_string_tag = WellKnownSymbols::to_string_tag();
41
42        let object = ObjectInitializer::new(context)
43            .function(Self::apply, "apply", 3)
44            .function(Self::construct, "construct", 2)
45            .function(Self::define_property, "defineProperty", 3)
46            .function(Self::delete_property, "deleteProperty", 2)
47            .function(Self::get, "get", 2)
48            .function(
49                Self::get_own_property_descriptor,
50                "getOwnPropertyDescriptor",
51                2,
52            )
53            .function(Self::get_prototype_of, "getPrototypeOf", 1)
54            .function(Self::has, "has", 2)
55            .function(Self::is_extensible, "isExtensible", 1)
56            .function(Self::own_keys, "ownKeys", 1)
57            .function(Self::prevent_extensions, "preventExtensions", 1)
58            .function(Self::set, "set", 3)
59            .function(Self::set_prototype_of, "setPrototypeOf", 3)
60            .property(
61                to_string_tag,
62                Self::NAME,
63                Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
64            )
65            .build();
66        (Self::NAME, object.into(), Self::attribute())
67    }
68}
69
70impl Reflect {
71    /// Calls a target function with arguments.
72    ///
73    /// More information:
74    ///  - [ECMAScript reference][spec]
75    ///  - [MDN documentation][mdn]
76    ///
77    /// [spec]: https://tc39.es/ecma262/#sec-reflect.apply
78    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/apply
79    pub(crate) fn apply(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
80        let target = args
81            .get(0)
82            .and_then(|v| v.as_object())
83            .ok_or_else(|| context.construct_type_error("target must be a function"))?;
84        let this_arg = args.get_or_undefined(1);
85        let args_list = args.get_or_undefined(2);
86
87        if !target.is_callable() {
88            return context.throw_type_error("target must be a function");
89        }
90        let args = args_list.create_list_from_array_like(&[], context)?;
91        target.call(this_arg, &args, context)
92    }
93
94    /// Calls a target function as a constructor with arguments.
95    ///
96    /// More information:
97    ///  - [ECMAScript reference][spec]
98    ///  - [MDN documentation][mdn]
99    ///
100    /// [spec]: https://tc39.es/ecma262/#sec-reflect.construct
101    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct
102    pub(crate) fn construct(
103        _: &JsValue,
104        args: &[JsValue],
105        context: &mut Context,
106    ) -> JsResult<JsValue> {
107        let target = args
108            .get(0)
109            .and_then(|v| v.as_object())
110            .ok_or_else(|| context.construct_type_error("target must be a function"))?;
111        let args_list = args.get_or_undefined(1);
112
113        if !target.is_constructable() {
114            return context.throw_type_error("target must be a constructor");
115        }
116
117        let new_target = if let Some(new_target) = args.get(2) {
118            if new_target.as_object().map(|o| o.is_constructable()) != Some(true) {
119                return context.throw_type_error("newTarget must be constructor");
120            }
121            new_target.clone()
122        } else {
123            target.clone().into()
124        };
125
126        let args = args_list.create_list_from_array_like(&[], context)?;
127        target.construct(&args, &new_target, context)
128    }
129
130    /// Defines a property on an object.
131    ///
132    /// More information:
133    ///  - [ECMAScript reference][spec]
134    ///  - [MDN documentation][mdn]
135    ///
136    /// [spec]: https://tc39.es/ecma262/#sec-reflect.defineProperty
137    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/defineProperty
138    pub(crate) fn define_property(
139        _: &JsValue,
140        args: &[JsValue],
141        context: &mut Context,
142    ) -> JsResult<JsValue> {
143        let target = args
144            .get(0)
145            .and_then(|v| v.as_object())
146            .ok_or_else(|| context.construct_type_error("target must be an object"))?;
147        let key = args.get_or_undefined(1).to_property_key(context)?;
148        let prop_desc: JsValue = args
149            .get(2)
150            .and_then(|v| v.as_object())
151            .ok_or_else(|| context.construct_type_error("property descriptor must be an object"))?
152            .into();
153
154        target
155            .__define_own_property__(key, prop_desc.to_property_descriptor(context)?, context)
156            .map(|b| b.into())
157    }
158
159    /// Defines a property on an object.
160    ///
161    /// More information:
162    ///  - [ECMAScript reference][spec]
163    ///  - [MDN documentation][mdn]
164    ///
165    /// [spec]: https://tc39.es/ecma262/#sec-reflect.deleteproperty
166    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/deleteProperty
167    pub(crate) fn delete_property(
168        _: &JsValue,
169        args: &[JsValue],
170        context: &mut Context,
171    ) -> JsResult<JsValue> {
172        let target = args
173            .get(0)
174            .and_then(|v| v.as_object())
175            .ok_or_else(|| context.construct_type_error("target must be an object"))?;
176        let key = args.get_or_undefined(1).to_property_key(context)?;
177
178        Ok(target.__delete__(&key, context)?.into())
179    }
180
181    /// Gets a property of an object.
182    ///
183    /// More information:
184    ///  - [ECMAScript reference][spec]
185    ///  - [MDN documentation][mdn]
186    ///
187    /// [spec]: https://tc39.es/ecma262/#sec-reflect.get
188    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get
189    pub(crate) fn get(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
190        // 1. If Type(target) is not Object, throw a TypeError exception.
191        let target = args
192            .get(0)
193            .and_then(|v| v.as_object())
194            .ok_or_else(|| context.construct_type_error("target must be an object"))?;
195        // 2. Let key be ? ToPropertyKey(propertyKey).
196        let key = args.get_or_undefined(1).to_property_key(context)?;
197        // 3. If receiver is not present, then
198        let receiver = if let Some(receiver) = args.get(2).cloned() {
199            receiver
200        } else {
201            // 3.a. Set receiver to target.
202            target.clone().into()
203        };
204        // 4. Return ? target.[[Get]](key, receiver).
205        target.__get__(&key, receiver, context)
206    }
207
208    /// Gets a property of an object.
209    ///
210    /// More information:
211    ///  - [ECMAScript reference][spec]
212    ///  - [MDN documentation][mdn]
213    ///
214    /// [spec]: https://tc39.es/ecma262/#sec-reflect.getownpropertydescriptor
215    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getOwnPropertyDescriptor
216    pub(crate) fn get_own_property_descriptor(
217        _: &JsValue,
218        args: &[JsValue],
219        context: &mut Context,
220    ) -> JsResult<JsValue> {
221        match args.get(0) {
222            Some(v) if v.is_object() => (),
223            _ => return context.throw_type_error("target must be an object"),
224        }
225        // This function is the same as Object.prototype.getOwnPropertyDescriptor, that why
226        // it is invoked here.
227        builtins::object::Object::get_own_property_descriptor(&JsValue::undefined(), args, context)
228    }
229
230    /// Gets the prototype of an object.
231    ///
232    /// More information:
233    ///  - [ECMAScript reference][spec]
234    ///  - [MDN documentation][mdn]
235    ///
236    /// [spec]: https://tc39.es/ecma262/#sec-reflect.getprototypeof
237    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getPrototypeOf
238    pub(crate) fn get_prototype_of(
239        _: &JsValue,
240        args: &[JsValue],
241        context: &mut Context,
242    ) -> JsResult<JsValue> {
243        let target = args
244            .get(0)
245            .and_then(|v| v.as_object())
246            .ok_or_else(|| context.construct_type_error("target must be an object"))?;
247        target.__get_prototype_of__(context)
248    }
249
250    /// Returns `true` if the object has the property, `false` otherwise.
251    ///
252    /// More information:
253    ///  - [ECMAScript reference][spec]
254    ///  - [MDN documentation][mdn]
255    ///
256    /// [spec]: https://tc39.es/ecma262/#sec-reflect.has
257    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/has
258    pub(crate) fn has(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
259        let target = args
260            .get(0)
261            .and_then(|v| v.as_object())
262            .ok_or_else(|| context.construct_type_error("target must be an object"))?;
263        let key = args
264            .get(1)
265            .unwrap_or(&JsValue::undefined())
266            .to_property_key(context)?;
267        Ok(target.__has_property__(&key, context)?.into())
268    }
269
270    /// Returns `true` if the object is extensible, `false` otherwise.
271    ///
272    /// More information:
273    ///  - [ECMAScript reference][spec]
274    ///  - [MDN documentation][mdn]
275    ///
276    /// [spec]: https://tc39.es/ecma262/#sec-reflect.isextensible
277    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/isExtensible
278    pub(crate) fn is_extensible(
279        _: &JsValue,
280        args: &[JsValue],
281        context: &mut Context,
282    ) -> JsResult<JsValue> {
283        let target = args
284            .get(0)
285            .and_then(|v| v.as_object())
286            .ok_or_else(|| context.construct_type_error("target must be an object"))?;
287        Ok(target.__is_extensible__(context)?.into())
288    }
289
290    /// Returns an array of object own property keys.
291    ///
292    /// More information:
293    ///  - [ECMAScript reference][spec]
294    ///  - [MDN documentation][mdn]
295    ///
296    /// [spec]: https://tc39.es/ecma262/#sec-reflect.ownkeys
297    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys
298    pub(crate) fn own_keys(
299        _: &JsValue,
300        args: &[JsValue],
301        context: &mut Context,
302    ) -> JsResult<JsValue> {
303        let target = args
304            .get(0)
305            .and_then(|v| v.as_object())
306            .ok_or_else(|| context.construct_type_error("target must be an object"))?;
307
308        let keys: Vec<JsValue> = target
309            .__own_property_keys__(context)?
310            .into_iter()
311            .map(|key| key.into())
312            .collect();
313
314        Ok(Array::create_array_from_list(keys, context).into())
315    }
316
317    /// Prevents new properties from ever being added to an object.
318    ///
319    /// More information:
320    ///  - [ECMAScript reference][spec]
321    ///  - [MDN documentation][mdn]
322    ///
323    /// [spec]: https://tc39.es/ecma262/#sec-reflect.preventextensions
324    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/preventExtensions
325    pub(crate) fn prevent_extensions(
326        _: &JsValue,
327        args: &[JsValue],
328        context: &mut Context,
329    ) -> JsResult<JsValue> {
330        let target = args
331            .get(0)
332            .and_then(|v| v.as_object())
333            .ok_or_else(|| context.construct_type_error("target must be an object"))?;
334
335        Ok(target.__prevent_extensions__(context)?.into())
336    }
337
338    /// Sets a property of an object.
339    ///
340    /// More information:
341    ///  - [ECMAScript reference][spec]
342    ///  - [MDN documentation][mdn]
343    ///
344    /// [spec]: https://tc39.es/ecma262/#sec-reflect.set
345    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set
346    pub(crate) fn set(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
347        let target = args
348            .get(0)
349            .and_then(|v| v.as_object())
350            .ok_or_else(|| context.construct_type_error("target must be an object"))?;
351        let key = args.get_or_undefined(1).to_property_key(context)?;
352        let value = args.get_or_undefined(2);
353        let receiver = if let Some(receiver) = args.get(3).cloned() {
354            receiver
355        } else {
356            target.clone().into()
357        };
358        Ok(target
359            .__set__(key, value.clone(), receiver, context)?
360            .into())
361    }
362
363    /// Sets the prototype of an object.
364    ///
365    /// More information:
366    ///  - [ECMAScript reference][spec]
367    ///  - [MDN documentation][mdn]
368    ///
369    /// [spec]: https://tc39.es/ecma262/#sec-reflect.setprototypeof
370    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/setPrototypeOf
371    pub(crate) fn set_prototype_of(
372        _: &JsValue,
373        args: &[JsValue],
374        context: &mut Context,
375    ) -> JsResult<JsValue> {
376        let mut target = args
377            .get(0)
378            .and_then(|v| v.as_object())
379            .ok_or_else(|| context.construct_type_error("target must be an object"))?;
380        let proto = args.get_or_undefined(1);
381        if !proto.is_null() && !proto.is_object() {
382            return context.throw_type_error("proto must be an object or null");
383        }
384        Ok(target.__set_prototype_of__(proto.clone(), context)?.into())
385    }
386}