using System.Diagnostics.CodeAnalysis; using Jint.Native; using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Runtime.Descriptors; namespace Jint.Runtime.Environments; /// /// Represents an object environment record /// https://tc39.es/ecma262/#sec-object-environment-records /// internal sealed class ObjectEnvironment : Environment { internal readonly ObjectInstance _bindingObject; private readonly bool _provideThis; private readonly bool _withEnvironment; public ObjectEnvironment( Engine engine, ObjectInstance bindingObject, bool provideThis, bool withEnvironment) : base(engine) { _bindingObject = bindingObject; _provideThis = provideThis; _withEnvironment = withEnvironment; } internal override bool HasBinding(Key name) => HasBinding(JsString.Create(name.Name)); internal override bool HasBinding(BindingName name) => HasBinding(name.Value); private bool HasBinding(JsString nameValue) { var foundBinding = _bindingObject.HasProperty(nameValue); if (!foundBinding) { return false; } if (!_withEnvironment) { return true; } return !IsBlocked(nameValue); } internal override bool TryGetBinding(BindingName name, bool strict, [NotNullWhen(true)] out JsValue? value) { // we unwrap by name if (!_bindingObject.HasProperty(name.Value)) { value = default; return false; } if (_withEnvironment && IsBlocked(name.Value)) { value = default; return false; } if (!_bindingObject.HasProperty(name.Value)) { if (strict) { // data was deleted during reading of unscopable information, of course... Throw.ReferenceNameError(_engine.Realm, name.Key); } } value = _bindingObject.Get(name.Value); return true; } private bool IsBlocked(JsValue property) { var unscopables = _bindingObject.Get(GlobalSymbolRegistry.Unscopables); if (unscopables is ObjectInstance oi) { var blocked = TypeConverter.ToBoolean(oi.Get(property)); if (blocked) { return true; } } return false; } /// /// http://www.ecma-international.org/ecma-262/6.0/#sec-object-environment-records-createmutablebinding-n-d /// internal override void CreateMutableBinding(Key name, bool canBeDeleted = false) { _bindingObject.DefinePropertyOrThrow(name.Name, new PropertyDescriptor(Undefined, canBeDeleted ? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding)); } /// /// https://tc39.es/ecma262/#sec-object-environment-records-createimmutablebinding-n-s /// internal override void CreateImmutableBinding(Key name, bool strict = true) { Throw.InvalidOperationException("The concrete Environment Record method CreateImmutableBinding is never used within this specification in association with Object Environment Records."); } /// /// https://tc39.es/ecma262/#sec-object-environment-records-initializebinding-n-v /// internal override void InitializeBinding(Key name, JsValue value, DisposeHint hint) => SetMutableBinding(name, value, strict: false); internal override void SetMutableBinding(Key name, JsValue value, bool strict) { var jsString = new JsString(name); if (!_bindingObject.HasProperty(jsString)) { if (strict) { Throw.ReferenceNameError(_engine.Realm, name); } } _bindingObject.Set(jsString, value, strict); } internal override void SetMutableBinding(BindingName name, JsValue value, bool strict) { if (!_bindingObject.HasProperty(name.Value)) { if (strict) { Throw.ReferenceNameError(_engine.Realm, name.Key); } } _bindingObject.Set(name.Value, value, strict); } internal override JsValue GetBindingValue(Key name, bool strict) { if (!_bindingObject.HasProperty(name.Name)) { if (strict) { Throw.ReferenceNameError(_engine.Realm, name.Name); } } return _bindingObject.Get(name.Name); } internal override bool DeleteBinding(Key name) => _bindingObject.Delete(name.Name); internal override bool HasThisBinding() => false; internal override bool HasSuperBinding() => false; internal override JsValue WithBaseObject() => _withEnvironment ? _bindingObject : Undefined; internal override bool HasBindings() => _bindingObject._properties?.Count > 0; internal override string[] GetAllBindingNames() { if (_bindingObject is not null) { var names = new List(_bindingObject._properties?.Count ?? 0); foreach (var name in _bindingObject.GetOwnProperties()) { names.Add(name.Key.ToString()); } return names.ToArray(); } return []; } public override bool Equals(JsValue? other) { return ReferenceEquals(_bindingObject, other); } internal override JsValue GetThisBinding() { throw new NotImplementedException(); } }