#pragma warning disable CA1859 // Use concrete types when possible for improved performance -- most of prototype methods return JsValue using Jint.Native.Array; using Jint.Native.Symbol; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Descriptors.Specialized; 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 LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "__defineGetter__", prototype.DefineGetter, 2, LengthFlags), PropertyFlags), ["__defineSetter__"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "__defineSetter__", prototype.DefineSetter, 2, LengthFlags), PropertyFlags), ["__lookupGetter__"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "__lookupGetter__", prototype.LookupGetter, 1, LengthFlags), PropertyFlags), ["__lookupSetter__"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "__lookupSetter__", prototype.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.RequireObjectCoercible(_engine, thisObject); var proto = arguments.At(0); if (!proto.IsObject() && !proto.IsNull() || thisObject is not ObjectInstance objectInstance) { return Undefined; } if (!objectInstance.SetPrototypeOf(proto)) { Throw.TypeError(_realm, "Invalid prototype"); } return Undefined; }, 0, LengthFlags), enumerable: false, configurable: true), ["toString"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "toString", prototype.ToObjectString, 0, LengthFlags), PropertyFlags), ["toLocaleString"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "toLocaleString", prototype.ToLocaleString, 0, LengthFlags), PropertyFlags), ["valueOf"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "valueOf", prototype.ValueOf, 0, LengthFlags), PropertyFlags), ["hasOwnProperty"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "hasOwnProperty", prototype.HasOwnProperty, 1, LengthFlags), PropertyFlags), ["isPrototypeOf"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "isPrototypeOf", prototype.IsPrototypeOf, 1, LengthFlags), PropertyFlags), ["propertyIsEnumerable"] = new LazyPropertyDescriptor(this, static prototype => new ClrFunction(prototype._engine, "propertyIsEnumerable", prototype.PropertyIsEnumerable, 1, LengthFlags), PropertyFlags) }; SetProperties(properties); } internal override bool SetPrototypeOf(JsValue value) { return SameValue(value, _prototype ?? Null); } 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, JsCallArguments arguments) { var o = TypeConverter.ToObject(_realm, thisObject); var p = arguments.At(0); var getter = arguments.At(1); if (!getter.IsCallable) { Throw.TypeError(_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, JsCallArguments arguments) { var o = TypeConverter.ToObject(_realm, thisObject); var p = arguments.At(0); var setter = arguments.At(1); if (!setter.IsCallable) { Throw.TypeError(_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, JsCallArguments 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, JsCallArguments 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, JsCallArguments 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, JsCallArguments arguments) { var o = TypeConverter.ToObject(_realm, thisObject); return o; } private JsValue IsPrototypeOf(JsValue thisObject, JsCallArguments 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 (v is null) { return JsBoolean.False; } if (ReferenceEquals(o, v)) { return JsBoolean.True; } } } /// /// https://tc39.es/ecma262/#sec-object.prototype.tolocalestring /// private JsValue ToLocaleString(JsValue thisObject, JsCallArguments arguments) { return Invoke(thisObject, "toString", []); } /// /// https://tc39.es/ecma262/#sec-object.prototype.tostring /// internal JsValue ToObjectString(JsValue thisObject, JsCallArguments 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, JsCallArguments arguments) { var p = TypeConverter.ToPropertyKey(arguments[0]); var o = TypeConverter.ToObject(_realm, thisObject); var desc = o.GetOwnProperty(p); return desc != PropertyDescriptor.Undefined; } }