using System.Collections.Generic;
using System.Linq;
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
///
public sealed class GlobalEnvironmentRecord : EnvironmentRecord
{
private readonly ObjectInstance _global;
private readonly DeclarativeEnvironmentRecord _declarativeRecord;
private readonly ObjectEnvironmentRecord _objectRecord;
private readonly HashSet _varNames = new HashSet();
public GlobalEnvironmentRecord(Engine engine, ObjectInstance global) : base(engine)
{
_global = global;
_objectRecord = new ObjectEnvironmentRecord(engine, global, provideThis: false, withEnvironment: false);
_declarativeRecord = new DeclarativeEnvironmentRecord(engine);
}
public ObjectInstance GlobalThisValue => _global;
public override bool HasBinding(string name)
{
return (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name)) || _objectRecord.HasBinding(name);
}
internal override bool TryGetBinding(
in BindingName name,
bool strict,
out Binding binding,
out JsValue value)
{
if (_declarativeRecord._hasBindings &&
_declarativeRecord.TryGetBinding(name, strict, out binding, out value))
{
return true;
}
// we unwrap by name
binding = default;
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;
}
return TryGetBindingForGlobalParent(name, out value, property);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private bool TryGetBindingForGlobalParent(
in BindingName name,
out JsValue value,
PropertyDescriptor property)
{
value = default;
var parent = _global._prototype;
if (parent is not null)
{
property = parent.GetOwnProperty(name.StringValue);
}
if (property == PropertyDescriptor.Undefined)
{
return false;
}
value = ObjectInstance.UnwrapJsValue(property, _global);
return true;
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-10.2.1.2.2
///
public override void CreateMutableBinding(string name, bool canBeDeleted = false)
{
if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
{
ExceptionHelper.ThrowTypeError(_engine.Realm, name + " has already been declared");
}
_declarativeRecord.CreateMutableBinding(name, canBeDeleted);
}
public override void CreateImmutableBinding(string name, bool strict = true)
{
if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
{
ExceptionHelper.ThrowTypeError(_engine.Realm, name + " has already been declared");
}
_declarativeRecord.CreateImmutableBinding(name, strict);
}
public override void InitializeBinding(string name, JsValue value)
{
if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
{
_declarativeRecord.InitializeBinding(name, value);
}
else
{
if (!_global.Set(name, value))
{
ExceptionHelper.ThrowTypeError(_engine.Realm);
}
}
}
public override void SetMutableBinding(string name, JsValue value, bool strict)
{
if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
{
_declarativeRecord.SetMutableBinding(name, value, strict);
}
else
{
// fast inlined path as we know we target global, otherwise would be
// _objectRecord.SetMutableBinding(name, value, strict);
if (!_global.Set(name, value) && strict)
{
ExceptionHelper.ThrowTypeError(_engine.Realm);
}
}
}
internal override void SetMutableBinding(in BindingName name, JsValue value, bool strict)
{
if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name.Key.Name))
{
_declarativeRecord.SetMutableBinding(name.Key.Name, value, strict);
}
else
{
if (_global is GlobalObject globalObject)
{
// fast inlined path as we know we target global
if (!globalObject.Set(name.Key, value) && strict)
{
ExceptionHelper.ThrowTypeError(_engine.Realm);
}
}
else
{
_objectRecord.SetMutableBinding(name, value ,strict);
}
}
}
public override JsValue GetBindingValue(string name, bool strict)
{
return _declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name)
? _declarativeRecord.GetBindingValue(name, strict)
: _objectRecord.GetBindingValue(name, strict);
}
public override bool DeleteBinding(string name)
{
if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
{
return _declarativeRecord.DeleteBinding(name);
}
if (_global.HasOwnProperty(name))
{
var status = _objectRecord.DeleteBinding(name);
if (status)
{
_varNames.Remove(name);
}
return status;
}
return true;
}
public override bool HasThisBinding()
{
return true;
}
public override bool HasSuperBinding()
{
return false;
}
public override JsValue WithBaseObject()
{
return Undefined;
}
public override JsValue GetThisBinding()
{
return _global;
}
public bool HasVarDeclaration(string name)
{
return _varNames.Contains(name);
}
public bool HasLexicalDeclaration(string name)
{
return _declarativeRecord.HasBinding(name);
}
public bool HasRestrictedGlobalProperty(string name)
{
var existingProp = _global.GetOwnProperty(name);
if (existingProp == PropertyDescriptor.Undefined)
{
return false;
}
return !existingProp.Configurable;
}
public bool CanDeclareGlobalVar(string name)
{
if (_global._properties.ContainsKey(name))
{
return true;
}
return _global.Extensible;
}
public bool CanDeclareGlobalFunction(string 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(string name, bool canBeDeleted)
{
var hasProperty = _global.HasOwnProperty(name);
if (!hasProperty && _global.Extensible)
{
_objectRecord.CreateMutableBindingAndInitialize(name, Undefined, canBeDeleted);
}
_varNames.Add(name);
}
public void CreateGlobalFunctionBinding(string name, JsValue value, bool canBeDeleted)
{
var existingProp = _global.GetOwnProperty(name);
PropertyDescriptor desc;
if (existingProp == PropertyDescriptor.Undefined || existingProp.Configurable)
{
desc = new PropertyDescriptor(value, true, true, canBeDeleted);
}
else
{
desc = new PropertyDescriptor(value, PropertyFlag.None);
}
_global.DefinePropertyOrThrow(name, desc);
_global.Set(name, value, false);
_varNames.Add(name);
}
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.
return _global.GetOwnProperties().Select(x => x.Key.ToString())
.Concat(_declarativeRecord.GetAllBindingNames()).ToArray();
}
public override bool Equals(JsValue other)
{
return ReferenceEquals(_objectRecord, other);
}
}
}