boa/environment/
global_environment_record.rs

1//! # Global Environment Records
2//!
3//! A global Environment Record is used to represent the outer most scope that is shared by all
4//! of the ECMAScript Script elements that are processed in a common realm.
5//! A global Environment Record provides the bindings for built-in globals (clause 18),
6//! properties of the global object, and for all top-level declarations (13.2.8, 13.2.10)
7//! that occur within a Script.
8//! More info:  <https://tc39.es/ecma262/#sec-global-environment-records>
9
10use crate::{
11    environment::{
12        declarative_environment_record::DeclarativeEnvironmentRecord,
13        environment_record_trait::EnvironmentRecordTrait,
14        lexical_environment::{Environment, EnvironmentType, VariableScope},
15        object_environment_record::ObjectEnvironmentRecord,
16    },
17    gc::{Finalize, Trace},
18    object::JsObject,
19    property::PropertyDescriptor,
20    Context, JsResult, JsValue,
21};
22use gc::{Gc, GcCell};
23use rustc_hash::FxHashSet;
24
25#[derive(Debug, Trace, Finalize, Clone)]
26pub struct GlobalEnvironmentRecord {
27    pub object_record: ObjectEnvironmentRecord,
28    pub global_this_binding: JsObject,
29    pub declarative_record: DeclarativeEnvironmentRecord,
30    pub var_names: GcCell<FxHashSet<Box<str>>>,
31}
32
33impl GlobalEnvironmentRecord {
34    pub fn new(global: JsObject, this_value: JsObject) -> GlobalEnvironmentRecord {
35        let obj_rec = ObjectEnvironmentRecord {
36            bindings: global,
37            outer_env: None,
38            /// Object Environment Records created for with statements (13.11)
39            /// can provide their binding object as an implicit this value for use in function calls.
40            /// The capability is controlled by a withEnvironment Boolean value that is associated
41            /// with each object Environment Record. By default, the value of withEnvironment is false
42            /// for any object Environment Record.
43            with_environment: false,
44        };
45
46        let dcl_rec = DeclarativeEnvironmentRecord::new(None);
47
48        GlobalEnvironmentRecord {
49            object_record: obj_rec,
50            global_this_binding: this_value,
51            declarative_record: dcl_rec,
52            var_names: GcCell::new(FxHashSet::default()),
53        }
54    }
55
56    /// `9.1.1.4.12 HasVarDeclaration ( N )`
57    ///
58    /// More information:
59    ///  - [ECMAScript reference][spec]
60    ///
61    /// [spec]: https://tc39.es/ecma262/#sec-hasvardeclaration
62    pub fn has_var_declaration(&self, name: &str) -> bool {
63        // 1. Let varDeclaredNames be envRec.[[VarNames]].
64        // 2. If varDeclaredNames contains N, return true.
65        // 3. Return false.
66        self.var_names.borrow().contains(name)
67    }
68
69    /// `9.1.1.4.13 HasLexicalDeclaration ( N )`
70    ///
71    /// More information:
72    ///  - [ECMAScript reference][spec]
73    ///
74    /// [spec]: https://tc39.es/ecma262/#sec-haslexicaldeclaration
75    pub fn has_lexical_declaration(&self, name: &str, context: &mut Context) -> JsResult<bool> {
76        // 1. Let DclRec be envRec.[[DeclarativeRecord]].
77        // 2. Return DclRec.HasBinding(N).
78        self.declarative_record.has_binding(name, context)
79    }
80
81    /// `9.1.1.4.14 HasRestrictedGlobalProperty ( N )`
82    ///
83    /// More information:
84    ///  - [ECMAScript reference][spec]
85    ///
86    /// [spec]: https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty
87    pub fn has_restricted_global_property(
88        &self,
89        name: &str,
90        context: &mut Context,
91    ) -> JsResult<bool> {
92        // 1. Let ObjRec be envRec.[[ObjectRecord]].
93        // 2. Let globalObject be ObjRec.[[BindingObject]].
94        let global_object = &self.object_record.bindings;
95
96        // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
97        let existing_prop = global_object.__get_own_property__(&name.into(), context)?;
98
99        if let Some(existing_prop) = existing_prop {
100            // 5. If existingProp.[[Configurable]] is true, return false.
101            // 6. Return true.
102            Ok(!existing_prop.expect_configurable())
103        } else {
104            // 4. If existingProp is undefined, return false.
105            Ok(false)
106        }
107    }
108
109    /// `9.1.1.4.15 CanDeclareGlobalVar ( N )`
110    ///
111    /// More information:
112    ///  - [ECMAScript reference][spec]
113    ///
114    /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar
115    pub fn can_declare_global_var(&self, name: &str, context: &mut Context) -> JsResult<bool> {
116        // 1. Let ObjRec be envRec.[[ObjectRecord]].
117        // 2. Let globalObject be ObjRec.[[BindingObject]].
118        let global_object = &self.object_record.bindings;
119
120        // 3. Let hasProperty be ? HasOwnProperty(globalObject, N).
121        let has_property = global_object.has_own_property(name, context)?;
122
123        // 4. If hasProperty is true, return true.
124        if has_property {
125            return Ok(true);
126        }
127
128        // 5. Return ? IsExtensible(globalObject).
129        global_object.is_extensible(context)
130    }
131
132    /// `9.1.1.4.16 CanDeclareGlobalFunction ( N )`
133    ///
134    /// More information:
135    ///  - [ECMAScript reference][spec]
136    ///
137    /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction
138    pub fn can_declare_global_function(&self, name: &str, context: &mut Context) -> JsResult<bool> {
139        // 1. Let ObjRec be envRec.[[ObjectRecord]].
140        // 2. Let globalObject be ObjRec.[[BindingObject]].
141        let global_object = &self.object_record.bindings;
142
143        // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
144        let existing_prop = global_object.__get_own_property__(&name.into(), context)?;
145
146        if let Some(existing_prop) = existing_prop {
147            // 5. If existingProp.[[Configurable]] is true, return true.
148            // 6. If IsDataDescriptor(existingProp) is true and existingProp has attribute values { [[Writable]]: true, [[Enumerable]]: true }, return true.
149            if existing_prop.expect_configurable()
150                || matches!(
151                    (
152                        existing_prop.is_data_descriptor(),
153                        existing_prop.writable(),
154                        existing_prop.enumerable(),
155                    ),
156                    (true, Some(true), Some(true))
157                )
158            {
159                Ok(true)
160            } else {
161                // 7. Return false.
162                Ok(false)
163            }
164        } else {
165            // 4. If existingProp is undefined, return ? IsExtensible(globalObject).
166            global_object.is_extensible(context)
167        }
168    }
169
170    /// `9.1.1.4.17 CreateGlobalVarBinding ( N, D )`
171    ///
172    /// More information:
173    ///  - [ECMAScript reference][spec]
174    ///
175    /// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding
176    pub fn create_global_var_binding(
177        &mut self,
178        name: &str,
179        deletion: bool,
180        context: &mut Context,
181    ) -> JsResult<()> {
182        // 1. Let ObjRec be envRec.[[ObjectRecord]].
183        // 2. Let globalObject be ObjRec.[[BindingObject]].
184        let global_object = &self.object_record.bindings;
185
186        // 3. Let hasProperty be ? HasOwnProperty(globalObject, N).
187        let has_property = global_object.has_own_property(name, context)?;
188        // 4. Let extensible be ? IsExtensible(globalObject).
189        let extensible = global_object.is_extensible(context)?;
190
191        // 5. If hasProperty is false and extensible is true, then
192        if !has_property && extensible {
193            // a. Perform ? ObjRec.CreateMutableBinding(N, D).
194            self.object_record
195                .create_mutable_binding(name, deletion, false, context)?;
196            // b. Perform ? ObjRec.InitializeBinding(N, undefined).
197            self.object_record
198                .initialize_binding(name, JsValue::undefined(), context)?;
199        }
200
201        // 6. Let varDeclaredNames be envRec.[[VarNames]].
202        let mut var_declared_names = self.var_names.borrow_mut();
203        // 7. If varDeclaredNames does not contain N, then
204        if !var_declared_names.contains(name) {
205            // a. Append N to varDeclaredNames.
206            var_declared_names.insert(name.into());
207        }
208
209        // 8. Return NormalCompletion(empty).
210        Ok(())
211    }
212
213    /// `9.1.1.4.18 CreateGlobalFunctionBinding ( N, V, D )`
214    ///
215    /// More information:
216    ///  - [ECMAScript reference][spec]
217    ///
218    /// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
219    pub fn create_global_function_binding(
220        &mut self,
221        name: &str,
222        value: JsValue,
223        deletion: bool,
224        context: &mut Context,
225    ) -> JsResult<()> {
226        // 1. Let ObjRec be envRec.[[ObjectRecord]].
227        // 2. Let globalObject be ObjRec.[[BindingObject]].
228        let global_object = &self.object_record.bindings;
229
230        // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
231        let existing_prop = global_object.__get_own_property__(&name.into(), context)?;
232
233        // 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then
234        let desc = if existing_prop
235            .map(|f| f.expect_configurable())
236            .unwrap_or(true)
237        {
238            // a. Let desc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }.
239            PropertyDescriptor::builder()
240                .value(value.clone())
241                .writable(true)
242                .enumerable(true)
243                .configurable(deletion)
244                .build()
245        // 5. Else,
246        } else {
247            // a. Let desc be the PropertyDescriptor { [[Value]]: V }.
248            PropertyDescriptor::builder().value(value.clone()).build()
249        };
250
251        // 6. Perform ? DefinePropertyOrThrow(globalObject, N, desc).
252        global_object.define_property_or_throw(name, desc, context)?;
253        // 7. Perform ? Set(globalObject, N, V, false).
254        global_object.set(name, value, false, context)?;
255
256        // 8. Let varDeclaredNames be envRec.[[VarNames]].
257        // 9. If varDeclaredNames does not contain N, then
258        if !self.var_names.borrow().contains(name) {
259            // a. Append N to varDeclaredNames.
260            self.var_names.borrow_mut().insert(name.into());
261        }
262
263        // 10. Return NormalCompletion(empty).
264        Ok(())
265    }
266}
267
268impl EnvironmentRecordTrait for GlobalEnvironmentRecord {
269    /// `9.1.1.4.1 HasBinding ( N )`
270    ///
271    /// More information:
272    ///  - [ECMAScript reference][spec]
273    ///
274    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-hasbinding-n
275    fn has_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
276        // 1. Let DclRec be envRec.[[DeclarativeRecord]].
277        // 2. If DclRec.HasBinding(N) is true, return true.
278        if self.declarative_record.has_binding(name, context)? {
279            return Ok(true);
280        }
281
282        // 3. Let ObjRec be envRec.[[ObjectRecord]].
283        // 4. Return ? ObjRec.HasBinding(N).
284        self.object_record.has_binding(name, context)
285    }
286
287    /// `9.1.1.4.2 CreateMutableBinding ( N, D )`
288    ///
289    /// More information:
290    ///  - [ECMAScript reference][spec]
291    ///
292    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-createmutablebinding-n-d
293    fn create_mutable_binding(
294        &self,
295        name: &str,
296        deletion: bool,
297        allow_name_reuse: bool,
298        context: &mut Context,
299    ) -> JsResult<()> {
300        // 1. Let DclRec be envRec.[[DeclarativeRecord]].
301        // 2. If DclRec.HasBinding(N) is true, throw a TypeError exception.
302        if !allow_name_reuse && self.declarative_record.has_binding(name, context)? {
303            return Err(
304                context.construct_type_error(format!("Binding already exists for {}", name))
305            );
306        }
307
308        // 3. Return DclRec.CreateMutableBinding(N, D).
309        self.declarative_record
310            .create_mutable_binding(name, deletion, allow_name_reuse, context)
311    }
312
313    /// `9.1.1.4.3 CreateImmutableBinding ( N, S )`
314    ///
315    /// More information:
316    ///  - [ECMAScript reference][spec]
317    ///
318    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-createimmutablebinding-n-s
319    fn create_immutable_binding(
320        &self,
321        name: &str,
322        strict: bool,
323        context: &mut Context,
324    ) -> JsResult<()> {
325        // 1. Let DclRec be envRec.[[DeclarativeRecord]].
326        // 2. If DclRec.HasBinding(N) is true, throw a TypeError exception.
327        if self.declarative_record.has_binding(name, context)? {
328            return Err(
329                context.construct_type_error(format!("Binding already exists for {}", name))
330            );
331        }
332
333        // 3. Return DclRec.CreateImmutableBinding(N, S).
334        self.declarative_record
335            .create_immutable_binding(name, strict, context)
336    }
337
338    /// `9.1.1.4.4 InitializeBinding ( N, V )`
339    ///
340    /// More information:
341    ///  - [ECMAScript reference][spec]
342    ///
343    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-initializebinding-n-v
344    fn initialize_binding(
345        &self,
346        name: &str,
347        value: JsValue,
348        context: &mut Context,
349    ) -> JsResult<()> {
350        // 1. Let DclRec be envRec.[[DeclarativeRecord]].
351        // 2. If DclRec.HasBinding(N) is true, then
352        if self.declarative_record.has_binding(name, context)? {
353            // a. Return DclRec.InitializeBinding(N, V).
354            return self
355                .declarative_record
356                .initialize_binding(name, value, context);
357        }
358
359        // 3. Assert: If the binding exists, it must be in the object Environment Record.
360        assert!(
361            self.object_record.has_binding(name, context)?,
362            "Binding must be in object_record"
363        );
364
365        // 4. Let ObjRec be envRec.[[ObjectRecord]].
366        // 5. Return ? ObjRec.InitializeBinding(N, V).
367        self.object_record.initialize_binding(name, value, context)
368    }
369
370    /// `9.1.1.4.5 SetMutableBinding ( N, V, S )`
371    ///
372    /// More information:
373    ///  - [ECMAScript reference][spec]
374    ///
375    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-setmutablebinding-n-v-s
376    fn set_mutable_binding(
377        &self,
378        name: &str,
379        value: JsValue,
380        strict: bool,
381        context: &mut Context,
382    ) -> JsResult<()> {
383        // 1. Let DclRec be envRec.[[DeclarativeRecord]].
384        // 2. If DclRec.HasBinding(N) is true, then
385        if self.declarative_record.has_binding(name, context)? {
386            // a. Return DclRec.SetMutableBinding(N, V, S).
387            return self
388                .declarative_record
389                .set_mutable_binding(name, value, strict, context);
390        }
391
392        // 3. Let ObjRec be envRec.[[ObjectRecord]].
393        // 4. Return ? ObjRec.SetMutableBinding(N, V, S).
394        self.object_record
395            .set_mutable_binding(name, value, strict, context)
396    }
397
398    /// `9.1.1.4.6 GetBindingValue ( N, S )`
399    ///
400    /// More information:
401    ///  - [ECMAScript reference][spec]
402    ///
403    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-getbindingvalue-n-s
404    fn get_binding_value(
405        &self,
406        name: &str,
407        strict: bool,
408        context: &mut Context,
409    ) -> JsResult<JsValue> {
410        // 1. Let DclRec be envRec.[[DeclarativeRecord]].
411        // 2. If DclRec.HasBinding(N) is true, then
412        if self.declarative_record.has_binding(name, context)? {
413            // a. Return DclRec.GetBindingValue(N, S).
414            return self
415                .declarative_record
416                .get_binding_value(name, strict, context);
417        }
418
419        // 3. Let ObjRec be envRec.[[ObjectRecord]].
420        // 4. Return ? ObjRec.GetBindingValue(N, S).
421        self.object_record.get_binding_value(name, strict, context)
422    }
423
424    /// `9.1.1.4.7 DeleteBinding ( N )`
425    ///
426    /// More information:
427    ///  - [ECMAScript reference][spec]
428    ///
429    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-deletebinding-n
430    fn delete_binding(&self, name: &str, context: &mut Context) -> JsResult<bool> {
431        // 1. Let DclRec be envRec.[[DeclarativeRecord]].
432        // 2. If DclRec.HasBinding(N) is true, then
433        if self.declarative_record.has_binding(name, context)? {
434            // a. Return DclRec.DeleteBinding(N).
435            return self.declarative_record.delete_binding(name, context);
436        }
437
438        // 3. Let ObjRec be envRec.[[ObjectRecord]].
439        // 4. Let globalObject be ObjRec.[[BindingObject]].
440        let global_object = &self.object_record.bindings;
441
442        // 5. Let existingProp be ? HasOwnProperty(globalObject, N).
443        // 6. If existingProp is true, then
444        if global_object.has_own_property(name, context)? {
445            // a. Let status be ? ObjRec.DeleteBinding(N).
446            let status = self.object_record.delete_binding(name, context)?;
447
448            // b. If status is true, then
449            if status {
450                // i. Let varNames be envRec.[[VarNames]].
451                // ii. If N is an element of varNames, remove that element from the varNames.
452                self.var_names.borrow_mut().remove(name);
453            }
454
455            // c. Return status.
456            return Ok(status);
457        }
458
459        // 7. Return true.
460        Ok(true)
461    }
462
463    /// `9.1.1.4.8 HasThisBinding ( )`
464    ///
465    /// More information:
466    ///  - [ECMAScript reference][spec]
467    ///
468    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-hasthisbinding
469    fn has_this_binding(&self) -> bool {
470        // 1. Return true.
471        true
472    }
473
474    /// `9.1.1.4.9 HasSuperBinding ( )`
475    ///
476    /// More information:
477    ///  - [ECMAScript reference][spec]
478    ///
479    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-hassuperbinding
480    fn has_super_binding(&self) -> bool {
481        // 1. Return false.
482        false
483    }
484
485    /// `9.1.1.4.10 WithBaseObject ( )`
486    ///
487    /// More information:
488    ///  - [ECMAScript reference][spec]
489    ///
490    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-withbaseobject
491    fn with_base_object(&self) -> Option<JsObject> {
492        // 1. Return undefined.
493        None
494    }
495
496    /// `9.1.1.4.11 GetThisBinding ( )`
497    ///
498    /// More information:
499    ///  - [ECMAScript reference][spec]
500    ///
501    /// [spec]: https://tc39.es/ecma262/#sec-global-environment-records-getthisbinding
502    fn get_this_binding(&self, _context: &mut Context) -> JsResult<JsValue> {
503        // 1. Return envRec.[[GlobalThisValue]].
504        Ok(self.global_this_binding.clone().into())
505    }
506
507    fn get_outer_environment(&self) -> Option<Environment> {
508        None
509    }
510
511    fn get_outer_environment_ref(&self) -> Option<&Environment> {
512        None
513    }
514
515    fn set_outer_environment(&mut self, _env: Environment) {
516        // TODO: Implement
517        todo!("Not implemented yet")
518    }
519
520    fn get_environment_type(&self) -> EnvironmentType {
521        EnvironmentType::Global
522    }
523
524    fn recursive_create_mutable_binding(
525        &self,
526        name: &str,
527        deletion: bool,
528        _scope: VariableScope,
529        context: &mut Context,
530    ) -> JsResult<()> {
531        self.create_mutable_binding(name, deletion, false, context)
532    }
533
534    fn recursive_create_immutable_binding(
535        &self,
536        name: &str,
537        deletion: bool,
538        _scope: VariableScope,
539        context: &mut Context,
540    ) -> JsResult<()> {
541        self.create_immutable_binding(name, deletion, context)
542    }
543
544    fn recursive_set_mutable_binding(
545        &self,
546        name: &str,
547        value: JsValue,
548        strict: bool,
549        context: &mut Context,
550    ) -> JsResult<()> {
551        self.set_mutable_binding(name, value, strict, context)
552    }
553
554    fn recursive_initialize_binding(
555        &self,
556        name: &str,
557        value: JsValue,
558        context: &mut Context,
559    ) -> JsResult<()> {
560        self.initialize_binding(name, value, context)
561    }
562}
563
564impl From<GlobalEnvironmentRecord> for Environment {
565    fn from(env: GlobalEnvironmentRecord) -> Environment {
566        Gc::new(Box::new(env))
567    }
568}