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}