using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Jint.Native; using Jint.Native.Global; using Jint.Native.Object; using Jint.Runtime.Descriptors; namespace Jint.Runtime.Environments; /// /// https://tc39.es/ecma262/#sec-global-environment-records /// internal sealed class GlobalEnvironment : Environment { /// /// A sealed class for global usage. /// internal sealed class GlobalDeclarativeEnvironment : DeclarativeEnvironment { public GlobalDeclarativeEnvironment(Engine engine) : base(engine) { } } internal readonly ObjectInstance _global; // we expect it to be GlobalObject, but need to allow to something host-defined, like Window private readonly GlobalObject? _globalObject; // Environment records are needed by debugger internal readonly GlobalDeclarativeEnvironment _declarativeRecord; public GlobalEnvironment(Engine engine, ObjectInstance global) : base(engine) { _global = global; _globalObject = global as GlobalObject; _declarativeRecord = new GlobalDeclarativeEnvironment(engine); } public ObjectInstance GlobalThisValue => _global; internal override bool HasBinding(Key name) { if (_declarativeRecord.HasBinding(name)) { return true; } if (_globalObject is not null) { return _globalObject.HasProperty(name); } return _global.HasProperty(new JsString(name)); } internal override bool HasBinding(BindingName name) { if (_declarativeRecord.HasBinding(name)) { return true; } if (_globalObject is not null) { return _globalObject.HasProperty(name.Key); } return _global.HasProperty(name.Value); } internal override bool TryGetBinding(BindingName name, bool strict, [NotNullWhen(true)] out JsValue? value) { if (_declarativeRecord._dictionary is not null && _declarativeRecord.TryGetBinding(name, strict, out value)) { return true; } // we unwrap by name value = default; // normal case is to find if (_global._properties!._dictionary.TryGetValue(name.Key, out var property) && property != PropertyDescriptor.Undefined) { value = ObjectInstance.UnwrapJsValue(property, _global); return true; } if (_global._prototype is not null) { return TryGetBindingForGlobalParent(name, out value); } return false; } [MethodImpl(MethodImplOptions.NoInlining)] private bool TryGetBindingForGlobalParent( BindingName name, [NotNullWhen(true)] out JsValue? value) { value = default; var parent = _global._prototype!; var property = parent.GetOwnProperty(name.Value); if (property == PropertyDescriptor.Undefined) { return false; } value = ObjectInstance.UnwrapJsValue(property, _global); return true; } /// /// https://tc39.es/ecma262/#sec-global-environment-records-createmutablebinding-n-d /// internal override void CreateMutableBinding(Key name, bool canBeDeleted = false) { if (_declarativeRecord.HasBinding(name)) { ThrowAlreadyDeclaredException(name); } _declarativeRecord.CreateMutableBinding(name, canBeDeleted); } /// /// https://tc39.es/ecma262/#sec-global-environment-records-createimmutablebinding-n-s /// internal override void CreateImmutableBinding(Key name, bool strict = true) { if (_declarativeRecord.HasBinding(name)) { ThrowAlreadyDeclaredException(name); } _declarativeRecord.CreateImmutableBinding(name, strict); } [MethodImpl(MethodImplOptions.NoInlining)] private void ThrowAlreadyDeclaredException(Key name) { Throw.TypeError(_engine.Realm, $"{name} has already been declared"); } internal override void InitializeBinding(Key name, JsValue value, DisposeHint hint) { if (_declarativeRecord.HasBinding(name)) { _declarativeRecord.InitializeBinding(name, value, hint); } else { _global._properties![name].Value = value; } } internal override void SetMutableBinding(Key name, JsValue value, bool strict) { if (_declarativeRecord.HasBinding(name)) { _declarativeRecord.SetMutableBinding(name, value, strict); } else { if (_globalObject is not null) { // fast inlined path as we know we target global if (!_globalObject.SetFromMutableBinding(name, value, strict) && strict) { Throw.TypeError(_engine.Realm); } } else { SetMutableBindingUnlikely(name, value, strict); } } } internal override void SetMutableBinding(BindingName name, JsValue value, bool strict) { if (_declarativeRecord.HasBinding(name)) { _declarativeRecord.SetMutableBinding(name, value, strict); } else { if (_globalObject is not null) { // fast inlined path as we know we target global if (!_globalObject.SetFromMutableBinding(name.Key, value, strict) && strict) { Throw.TypeError(_engine.Realm); } } else { SetMutableBindingUnlikely(name.Key, value, strict); } } } private void SetMutableBindingUnlikely(Key name, JsValue value, bool strict) { // see ObjectEnvironmentRecord.SetMutableBinding var jsString = new JsString(name.Name); if (strict && !_global.HasProperty(jsString)) { Throw.ReferenceNameError(_engine.Realm, name); } _global.Set(jsString, value); } internal override JsValue GetBindingValue(Key name, bool strict) { if (_declarativeRecord.HasBinding(name)) { return _declarativeRecord.GetBindingValue(name, strict); } // see ObjectEnvironmentRecord.GetBindingValue var desc = PropertyDescriptor.Undefined; if (_globalObject is not null) { if (_globalObject._properties?.TryGetValue(name, out desc) == false) { desc = PropertyDescriptor.Undefined; } } else { desc = _global.GetOwnProperty(name.Name); } if (strict && desc == PropertyDescriptor.Undefined) { Throw.ReferenceNameError(_engine.Realm, name); } return ObjectInstance.UnwrapJsValue(desc, _global); } internal override bool DeleteBinding(Key name) { if (_declarativeRecord.HasBinding(name)) { return _declarativeRecord.DeleteBinding(name); } var n = JsString.Create(name.Name); if (_global.HasOwnProperty(n)) { return _global.Delete(n); } return true; } internal override bool HasThisBinding() => true; internal override bool HasSuperBinding() => false; internal override JsValue WithBaseObject() => Undefined; internal override JsValue GetThisBinding() => _global; internal bool HasLexicalDeclaration(Key name) => _declarativeRecord.HasBinding(name); internal bool HasRestrictedGlobalProperty(Key name) { if (_globalObject is not null) { return _globalObject._properties?.TryGetValue(name, out var desc) == true && !desc.Configurable; } var existingProp = _global.GetOwnProperty(name.Name); if (existingProp == PropertyDescriptor.Undefined) { return false; } return !existingProp.Configurable; } public bool CanDeclareGlobalVar(Key name) { if (_global._properties!.ContainsKey(name)) { return true; } return _global.Extensible; } public bool CanDeclareGlobalFunction(Key name) { if (!_global._properties!.TryGetValue(name, out var existingProp) || existingProp == PropertyDescriptor.Undefined) { return _global.Extensible; } if (existingProp.Configurable) { return true; } if (existingProp.IsDataDescriptor() && existingProp.Writable && existingProp.Enumerable) { return true; } return false; } public void CreateGlobalVarBinding(Key name, bool canBeDeleted) { if (!_global.Extensible) { return; } _global._properties!.TryAdd(name, new PropertyDescriptor(Undefined, canBeDeleted ? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding)); } internal void CreateGlobalVarBindings(List names, bool canBeDeleted) { if (!_global.Extensible) { return; } for (var i = 0; i < names.Count; i++) { var name = names[i]; _global._properties!.TryAdd(name, new PropertyDescriptor(Undefined, canBeDeleted ? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding)); } } /// /// https://tc39.es/ecma262/#sec-createglobalfunctionbinding /// public void CreateGlobalFunctionBinding(Key name, JsValue value, bool canBeDeleted) { var jsString = new JsString(name); var existingProp = _global.GetOwnProperty(jsString); PropertyDescriptor desc; if (existingProp == PropertyDescriptor.Undefined || existingProp.Configurable) { desc = new PropertyDescriptor(value, true, true, canBeDeleted); } else { desc = new PropertyDescriptor(value, PropertyFlag.None); } _global.DefinePropertyOrThrow(jsString, desc); _global.Set(jsString, value, false); } internal override bool HasBindings() { return _declarativeRecord.HasBindings() || _globalObject?._properties?.Count > 0 || _global._properties?.Count > 0; } internal override string[] GetAllBindingNames() { // JT: Rather than introduce a new method for the debugger, I'm reusing this one, // which - in spite of the very general name - is actually only used by the debugger // at this point. var names = new List(_global._properties?.Count ?? 0 + _declarativeRecord._dictionary?.Count ?? 0); foreach (var name in _global.GetOwnProperties()) { names.Add(name.Key.ToString()); } foreach (var name in _declarativeRecord.GetAllBindingNames()) { names.Add(name); } return names.ToArray(); } public override bool Equals(JsValue? other) { return ReferenceEquals(this, other); } }