#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue
using Jint.Collections;
using Jint.Native.Array;
using Jint.Native.Symbol;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;
namespace Jint.Native.Object
{
public sealed class ObjectPrototype : Prototype
{
private readonly ObjectConstructor _constructor;
internal ObjectChangeFlags _objectChangeFlags;
internal ObjectPrototype(
Engine engine,
Realm realm,
ObjectConstructor constructor) : base(engine, realm)
{
_constructor = constructor;
}
protected override void Initialize()
{
const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
const PropertyFlag lengthFlags = PropertyFlag.Configurable;
var properties = new PropertyDictionary(12, checkExistingKeys: false)
{
["constructor"] = new PropertyDescriptor(_constructor, propertyFlags),
["__defineGetter__"] = new PropertyDescriptor(new ClrFunction(Engine, "__defineGetter__", DefineGetter, 2, lengthFlags), propertyFlags),
["__defineSetter__"] = new PropertyDescriptor(new ClrFunction(Engine, "__defineSetter__", DefineSetter, 2, lengthFlags), propertyFlags),
["__lookupGetter__"] = new PropertyDescriptor(new ClrFunction(Engine, "__lookupGetter__", LookupGetter, 1, lengthFlags), propertyFlags),
["__lookupSetter__"] = new PropertyDescriptor(new ClrFunction(Engine, "__lookupSetter__", LookupSetter, 1, lengthFlags), propertyFlags),
["__proto__"] = new GetSetPropertyDescriptor(
new ClrFunction(Engine, "get __proto__", (thisObject, _) => TypeConverter.ToObject(_realm, thisObject).GetPrototypeOf() ?? Null, 0, lengthFlags),
new ClrFunction(Engine, "set __proto__", (thisObject, arguments) =>
{
TypeConverter.CheckObjectCoercible(_engine, thisObject);
var proto = arguments.At(0);
if (!proto.IsObject() && !proto.IsNull() || thisObject is not ObjectInstance objectInstance)
{
return Undefined;
}
if (!objectInstance.SetPrototypeOf(proto))
{
ExceptionHelper.ThrowTypeError(_realm, "Invalid prototype");
}
return Undefined;
}, 0, lengthFlags),
enumerable: false, configurable: true),
["toString"] = new PropertyDescriptor(new ClrFunction(Engine, "toString", ToObjectString, 0, lengthFlags), propertyFlags),
["toLocaleString"] = new PropertyDescriptor(new ClrFunction(Engine, "toLocaleString", ToLocaleString, 0, lengthFlags), propertyFlags),
["valueOf"] = new PropertyDescriptor(new ClrFunction(Engine, "valueOf", ValueOf, 0, lengthFlags), propertyFlags),
["hasOwnProperty"] = new PropertyDescriptor(new ClrFunction(Engine, "hasOwnProperty", HasOwnProperty, 1, lengthFlags), propertyFlags),
["isPrototypeOf"] = new PropertyDescriptor(new ClrFunction(Engine, "isPrototypeOf", IsPrototypeOf, 1, lengthFlags), propertyFlags),
["propertyIsEnumerable"] = new PropertyDescriptor(new ClrFunction(Engine, "propertyIsEnumerable", PropertyIsEnumerable, 1, lengthFlags), propertyFlags)
};
SetProperties(properties);
}
public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
{
TrackChanges(property);
return base.DefineOwnProperty(property, desc);
}
protected internal override void SetOwnProperty(JsValue property, PropertyDescriptor desc)
{
TrackChanges(property);
base.SetOwnProperty(property, desc);
}
private void TrackChanges(JsValue property)
{
EnsureInitialized();
if (ArrayInstance.IsArrayIndex(property, out _))
{
_objectChangeFlags |= ObjectChangeFlags.ArrayIndex;
}
else
{
_objectChangeFlags |= property.IsSymbol() ? ObjectChangeFlags.Symbol : ObjectChangeFlags.Property;
}
}
///
/// https://tc39.es/ecma262/#sec-object.prototype.__defineGetter__
///
private JsValue DefineGetter(JsValue thisObject, JsValue[] arguments)
{
var o = TypeConverter.ToObject(_realm, thisObject);
var p = arguments.At(0);
var getter = arguments.At(1);
if (!getter.IsCallable)
{
ExceptionHelper.ThrowTypeError(_realm, "Target is not callable");
}
var desc = new GetSetPropertyDescriptor(getter, null, enumerable: true, configurable: true);
var key = TypeConverter.ToPropertyKey(p);
o.DefinePropertyOrThrow(key, desc);
return Undefined;
}
///
/// https://tc39.es/ecma262/#sec-object.prototype.__defineSetter__
///
private JsValue DefineSetter(JsValue thisObject, JsValue[] arguments)
{
var o = TypeConverter.ToObject(_realm, thisObject);
var p = arguments.At(0);
var setter = arguments.At(1);
if (!setter.IsCallable)
{
ExceptionHelper.ThrowTypeError(_realm, "Target is not callable");
}
var desc = new GetSetPropertyDescriptor(null, setter, enumerable: true, configurable: true);
var key = TypeConverter.ToPropertyKey(p);
o.DefinePropertyOrThrow(key, desc);
return Undefined;
}
///
/// https://tc39.es/ecma262/#sec-object.prototype.__lookupGetter__
///
private JsValue LookupGetter(JsValue thisObject, JsValue[] arguments)
{
var o = TypeConverter.ToObject(_realm, thisObject);
var key = TypeConverter.ToPropertyKey(arguments.At(0));
while (true)
{
var desc = o.GetOwnProperty(key);
if (!ReferenceEquals(desc, PropertyDescriptor.Undefined))
{
if (desc.IsAccessorDescriptor())
{
return desc.Get ?? Undefined;
}
return Undefined;
}
o = o.GetPrototypeOf();
if (o is null)
{
return Undefined;
}
}
}
///
/// https://tc39.es/ecma262/#sec-object.prototype.__lookupSetter__
///
private JsValue LookupSetter(JsValue thisObject, JsValue[] arguments)
{
var o = TypeConverter.ToObject(_realm, thisObject);
var key = TypeConverter.ToPropertyKey(arguments.At(0));
while (true)
{
var desc = o.GetOwnProperty(key);
if (!ReferenceEquals(desc, PropertyDescriptor.Undefined))
{
if (desc.IsAccessorDescriptor())
{
return desc.Set ?? Undefined;
}
return Undefined;
}
o = o.GetPrototypeOf();
if (o is null)
{
return Undefined;
}
}
}
private JsValue PropertyIsEnumerable(JsValue thisObject, JsValue[] arguments)
{
var p = TypeConverter.ToPropertyKey(arguments[0]);
var o = TypeConverter.ToObject(_realm, thisObject);
var desc = o.GetOwnProperty(p);
if (desc == PropertyDescriptor.Undefined)
{
return JsBoolean.False;
}
return desc.Enumerable;
}
private JsValue ValueOf(JsValue thisObject, JsValue[] arguments)
{
var o = TypeConverter.ToObject(_realm, thisObject);
return o;
}
private JsValue IsPrototypeOf(JsValue thisObject, JsValue[] arguments)
{
var arg = arguments[0];
if (!arg.IsObject())
{
return JsBoolean.False;
}
var v = arg.AsObject();
var o = TypeConverter.ToObject(_realm, thisObject);
while (true)
{
v = v.Prototype;
if (ReferenceEquals(v, null))
{
return JsBoolean.False;
}
if (ReferenceEquals(o, v))
{
return JsBoolean.True;
}
}
}
///
/// https://tc39.es/ecma262/#sec-object.prototype.tolocalestring
///
private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
{
return Invoke(thisObject, "toString", System.Array.Empty());
}
///
/// https://tc39.es/ecma262/#sec-object.prototype.tostring
///
internal JsValue ToObjectString(JsValue thisObject, JsValue[] arguments)
{
if (thisObject.IsUndefined())
{
return "[object Undefined]";
}
if (thisObject.IsNull())
{
return "[object Null]";
}
var o = TypeConverter.ToObject(_realm, thisObject);
var isArray = o.IsArray();
var tag = o.Get(GlobalSymbolRegistry.ToStringTag);
if (!tag.IsString())
{
if (isArray)
{
tag = "Array";
}
else if (o.IsCallable)
{
tag = "Function";
}
else
{
tag = (o is JsProxy ? ObjectClass.Object : o.Class).ToString();
}
}
return "[object " + tag + "]";
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.4.5
///
private JsValue HasOwnProperty(JsValue thisObject, JsValue[] arguments)
{
var p = TypeConverter.ToPropertyKey(arguments[0]);
var o = TypeConverter.ToObject(_realm, thisObject);
var desc = o.GetOwnProperty(p);
return desc != PropertyDescriptor.Undefined;
}
}
}