|
@@ -12,16 +12,19 @@ namespace Jint.Runtime.Environments
|
|
|
/// </summary>
|
|
|
public sealed class GlobalEnvironmentRecord : EnvironmentRecord
|
|
|
{
|
|
|
- private readonly ObjectInstance _global;
|
|
|
+ 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 DeclarativeEnvironmentRecord _declarativeRecord;
|
|
|
- internal readonly ObjectEnvironmentRecord _objectRecord;
|
|
|
- private readonly HashSet<string> _varNames = new HashSet<string>();
|
|
|
+ private readonly HashSet<string> _varNames = new();
|
|
|
|
|
|
public GlobalEnvironmentRecord(Engine engine, ObjectInstance global) : base(engine)
|
|
|
{
|
|
|
_global = global;
|
|
|
- _objectRecord = new ObjectEnvironmentRecord(engine, global, provideThis: false, withEnvironment: false);
|
|
|
+ _globalObject = global as GlobalObject;
|
|
|
_declarativeRecord = new DeclarativeEnvironmentRecord(engine);
|
|
|
}
|
|
|
|
|
@@ -29,7 +32,32 @@ namespace Jint.Runtime.Environments
|
|
|
|
|
|
public override bool HasBinding(string name)
|
|
|
{
|
|
|
- return (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name)) || _objectRecord.HasBinding(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(in BindingName name)
|
|
|
+ {
|
|
|
+ if (_declarativeRecord.HasBinding(name))
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_globalObject is not null)
|
|
|
+ {
|
|
|
+ return _globalObject.HasProperty(name.Key);
|
|
|
+ }
|
|
|
+
|
|
|
+ return _global.HasProperty(name.StringValue);
|
|
|
}
|
|
|
|
|
|
internal override bool TryGetBinding(
|
|
@@ -38,8 +66,7 @@ namespace Jint.Runtime.Environments
|
|
|
out Binding binding,
|
|
|
[NotNullWhen(true)] out JsValue? value)
|
|
|
{
|
|
|
- if (_declarativeRecord._hasBindings &&
|
|
|
- _declarativeRecord.TryGetBinding(name, strict, out binding, out value))
|
|
|
+ if (_declarativeRecord.TryGetBinding(name, strict, out binding, out value))
|
|
|
{
|
|
|
return true;
|
|
|
}
|
|
@@ -56,22 +83,23 @@ namespace Jint.Runtime.Environments
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- return TryGetBindingForGlobalParent(name, out value, property);
|
|
|
+ if (_global._prototype is not null)
|
|
|
+ {
|
|
|
+ return TryGetBindingForGlobalParent(name, out value);
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
|
private bool TryGetBindingForGlobalParent(
|
|
|
in BindingName name,
|
|
|
- [NotNullWhen(true)] out JsValue? value,
|
|
|
- PropertyDescriptor property)
|
|
|
+ [NotNullWhen(true)] out JsValue? value)
|
|
|
{
|
|
|
value = default;
|
|
|
|
|
|
- var parent = _global._prototype;
|
|
|
- if (parent is not null)
|
|
|
- {
|
|
|
- property = parent.GetOwnProperty(name.StringValue);
|
|
|
- }
|
|
|
+ var parent = _global._prototype!;
|
|
|
+ var property = parent.GetOwnProperty(name.StringValue);
|
|
|
|
|
|
if (property == PropertyDescriptor.Undefined)
|
|
|
{
|
|
@@ -83,110 +111,177 @@ namespace Jint.Runtime.Environments
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// http://www.ecma-international.org/ecma-262/5.1/#sec-10.2.1.2.2
|
|
|
+ /// https://tc39.es/ecma262/#sec-global-environment-records-createmutablebinding-n-d
|
|
|
/// </summary>
|
|
|
public override void CreateMutableBinding(string name, bool canBeDeleted = false)
|
|
|
{
|
|
|
- if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
|
|
|
+ if (_declarativeRecord.HasBinding(name))
|
|
|
{
|
|
|
- ExceptionHelper.ThrowTypeError(_engine.Realm, name + " has already been declared");
|
|
|
+ ThrowAlreadyDeclaredException(name);
|
|
|
}
|
|
|
|
|
|
_declarativeRecord.CreateMutableBinding(name, canBeDeleted);
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// https://tc39.es/ecma262/#sec-global-environment-records-createimmutablebinding-n-s
|
|
|
+ /// </summary>
|
|
|
public override void CreateImmutableBinding(string name, bool strict = true)
|
|
|
{
|
|
|
- if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
|
|
|
+ if (_declarativeRecord.HasBinding(name))
|
|
|
{
|
|
|
- ExceptionHelper.ThrowTypeError(_engine.Realm, name + " has already been declared");
|
|
|
+ ThrowAlreadyDeclaredException(name);
|
|
|
}
|
|
|
|
|
|
_declarativeRecord.CreateImmutableBinding(name, strict);
|
|
|
}
|
|
|
|
|
|
+ [MethodImpl(MethodImplOptions.NoInlining)]
|
|
|
+ private void ThrowAlreadyDeclaredException(string name)
|
|
|
+ {
|
|
|
+ ExceptionHelper.ThrowTypeError(_engine.Realm, name + " has already been declared");
|
|
|
+ }
|
|
|
+
|
|
|
public override void InitializeBinding(string name, JsValue value)
|
|
|
{
|
|
|
- if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
|
|
|
+ if (_declarativeRecord.HasBinding(name))
|
|
|
{
|
|
|
_declarativeRecord.InitializeBinding(name, value);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- if (!_global.Set(name, value))
|
|
|
- {
|
|
|
- ExceptionHelper.ThrowTypeError(_engine.Realm);
|
|
|
- }
|
|
|
+ _global._properties![name].Value = value;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public override void SetMutableBinding(string name, JsValue value, bool strict)
|
|
|
{
|
|
|
- if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
|
|
|
+ if (_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);
|
|
|
- var property = JsString.Create(name);
|
|
|
- if (strict && !_global.HasProperty(property))
|
|
|
+ if (_globalObject is not null)
|
|
|
{
|
|
|
- ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name);
|
|
|
+ // fast inlined path as we know we target global
|
|
|
+ if (!_globalObject.SetFromMutableBinding(name, value) && strict)
|
|
|
+ {
|
|
|
+ ExceptionHelper.ThrowTypeError(_engine.Realm);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ SetMutableBindingUnlikely(name, value, strict);
|
|
|
}
|
|
|
-
|
|
|
- _global.Set(property, value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
internal override void SetMutableBinding(in BindingName name, JsValue value, bool strict)
|
|
|
{
|
|
|
- if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name.Key.Name))
|
|
|
+ if (_declarativeRecord.HasBinding(name))
|
|
|
{
|
|
|
- _declarativeRecord.SetMutableBinding(name.Key.Name, value, strict);
|
|
|
+ _declarativeRecord.SetMutableBinding(name, value, strict);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- if (_global is GlobalObject globalObject)
|
|
|
+ if (_globalObject is not null)
|
|
|
{
|
|
|
// fast inlined path as we know we target global
|
|
|
- if (!globalObject.Set(name.Key, value) && strict)
|
|
|
+ if (!_globalObject.SetFromMutableBinding(name.Key, value) && strict)
|
|
|
{
|
|
|
ExceptionHelper.ThrowTypeError(_engine.Realm);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- _objectRecord.SetMutableBinding(name, value ,strict);
|
|
|
+ SetMutableBindingUnlikely(name.Key.Name, value, strict);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private void SetMutableBindingUnlikely(string name, JsValue value, bool strict)
|
|
|
+ {
|
|
|
+ // see ObjectEnvironmentRecord.SetMutableBinding
|
|
|
+ var jsString = new JsString(name);
|
|
|
+ if (strict && !_global.HasProperty(jsString))
|
|
|
+ {
|
|
|
+ ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name);
|
|
|
+ }
|
|
|
+
|
|
|
+ _global.Set(jsString, value);
|
|
|
+ }
|
|
|
+
|
|
|
public override JsValue GetBindingValue(string name, bool strict)
|
|
|
{
|
|
|
- return _declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name)
|
|
|
- ? _declarativeRecord.GetBindingValue(name, strict)
|
|
|
- : _objectRecord.GetBindingValue(name, 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.GetProperty(name);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (strict && desc == PropertyDescriptor.Undefined)
|
|
|
+ {
|
|
|
+ ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ObjectInstance.UnwrapJsValue(desc, _global);
|
|
|
}
|
|
|
|
|
|
internal override bool TryGetBindingValue(string name, bool strict, [NotNullWhen(true)] out JsValue? value)
|
|
|
{
|
|
|
- return _declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name)
|
|
|
- ? _declarativeRecord.TryGetBindingValue(name, strict, out value)
|
|
|
- : _objectRecord.TryGetBindingValue(name, strict, out value);
|
|
|
+ if (_declarativeRecord.HasBinding(name))
|
|
|
+ {
|
|
|
+ return _declarativeRecord.TryGetBindingValue(name, strict, out value);
|
|
|
+ }
|
|
|
+
|
|
|
+ // see ObjectEnvironmentRecord.TryGetBindingValue
|
|
|
+ var desc = PropertyDescriptor.Undefined;
|
|
|
+ if (_globalObject is not null)
|
|
|
+ {
|
|
|
+ if (_globalObject._properties?.TryGetValue(name, out desc) == false)
|
|
|
+ {
|
|
|
+ desc = PropertyDescriptor.Undefined;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ desc = _global.GetProperty(name);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (strict && desc == PropertyDescriptor.Undefined)
|
|
|
+ {
|
|
|
+ value = null;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ value = ObjectInstance.UnwrapJsValue(desc, _global);
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
public override bool DeleteBinding(string name)
|
|
|
{
|
|
|
- if (_declarativeRecord._hasBindings && _declarativeRecord.HasBinding(name))
|
|
|
+ if (_declarativeRecord.HasBinding(name))
|
|
|
{
|
|
|
return _declarativeRecord.DeleteBinding(name);
|
|
|
}
|
|
|
|
|
|
if (_global.HasOwnProperty(name))
|
|
|
{
|
|
|
- var status = _objectRecord.DeleteBinding(name);
|
|
|
+ var status = _global.Delete(name);
|
|
|
if (status)
|
|
|
{
|
|
|
_varNames.Remove(name);
|
|
@@ -230,6 +325,12 @@ namespace Jint.Runtime.Environments
|
|
|
|
|
|
public bool HasRestrictedGlobalProperty(string name)
|
|
|
{
|
|
|
+ if (_globalObject is not null)
|
|
|
+ {
|
|
|
+ return _globalObject._properties?.TryGetValue(name, out var desc) == true
|
|
|
+ && !desc.Configurable;
|
|
|
+ }
|
|
|
+
|
|
|
var existingProp = _global.GetOwnProperty(name);
|
|
|
if (existingProp == PropertyDescriptor.Undefined)
|
|
|
{
|
|
@@ -272,10 +373,12 @@ namespace Jint.Runtime.Environments
|
|
|
|
|
|
public void CreateGlobalVarBinding(string name, bool canBeDeleted)
|
|
|
{
|
|
|
- var hasProperty = _global.HasOwnProperty(name);
|
|
|
- if (!hasProperty && _global.Extensible)
|
|
|
+ Key key = name;
|
|
|
+ if (!_global._properties!.ContainsKey(key) && _global.Extensible)
|
|
|
{
|
|
|
- _objectRecord.CreateMutableBindingAndInitialize(name, Undefined, canBeDeleted);
|
|
|
+ _global._properties[key] = new PropertyDescriptor(Undefined, canBeDeleted
|
|
|
+ ? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding
|
|
|
+ : PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding);
|
|
|
}
|
|
|
|
|
|
_varNames.Add(name);
|
|
@@ -286,7 +389,8 @@ namespace Jint.Runtime.Environments
|
|
|
/// </summary>
|
|
|
public void CreateGlobalFunctionBinding(string name, JsValue value, bool canBeDeleted)
|
|
|
{
|
|
|
- var existingProp = _global.GetOwnProperty(name);
|
|
|
+ var jsString = new JsString(name);
|
|
|
+ var existingProp = _global.GetOwnProperty(jsString);
|
|
|
|
|
|
PropertyDescriptor desc;
|
|
|
if (existingProp == PropertyDescriptor.Undefined || existingProp.Configurable)
|
|
@@ -298,8 +402,8 @@ namespace Jint.Runtime.Environments
|
|
|
desc = new PropertyDescriptor(value, PropertyFlag.None);
|
|
|
}
|
|
|
|
|
|
- _global.DefinePropertyOrThrow(name, desc);
|
|
|
- _global.Set(name, value, false);
|
|
|
+ _global.DefinePropertyOrThrow(jsString, desc);
|
|
|
+ _global.Set(jsString, value, false);
|
|
|
_varNames.Add(name);
|
|
|
}
|
|
|
|
|
@@ -308,13 +412,23 @@ namespace Jint.Runtime.Environments
|
|
|
// 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();
|
|
|
+ var names = new List<string>(_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(_objectRecord, other);
|
|
|
+ return ReferenceEquals(this, other);
|
|
|
}
|
|
|
}
|
|
|
}
|