using Jint.Collections; using Jint.Native.Function; using Jint.Native.Iterator; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; namespace Jint.Native.Object { public sealed class ObjectConstructor : FunctionInstance, IConstructor { private static readonly JsString _name = new JsString("Object"); internal ObjectConstructor( Engine engine, Realm realm) : base(engine, realm, _name) { PrototypeObject = new ObjectPrototype(engine, realm, this); _length = PropertyDescriptor.AllForbiddenDescriptor.NumberOne; _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden); } public ObjectPrototype PrototypeObject { get; } protected override void Initialize() { _prototype = _realm.Intrinsics.Function.PrototypeObject; const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable; const PropertyFlag lengthFlags = PropertyFlag.Configurable; var properties = new PropertyDictionary(15, checkExistingKeys: false) { ["assign"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "assign", Assign, 2, lengthFlags), propertyFlags), ["entries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "entries", Entries, 1, lengthFlags), propertyFlags), ["fromEntries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromEntries", FromEntries, 1, lengthFlags), propertyFlags), ["getPrototypeOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getPrototypeOf", GetPrototypeOf, 1), propertyFlags), ["getOwnPropertyDescriptor"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptor", GetOwnPropertyDescriptor, 2, lengthFlags), propertyFlags), ["getOwnPropertyDescriptors"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptors", GetOwnPropertyDescriptors, 1, lengthFlags), propertyFlags), ["getOwnPropertyNames"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyNames", GetOwnPropertyNames, 1), propertyFlags), ["getOwnPropertySymbols"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertySymbols", GetOwnPropertySymbols, 1, lengthFlags), propertyFlags), ["create"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "create", Create, 2), propertyFlags), ["defineProperty"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "defineProperty", DefineProperty, 3), propertyFlags), ["defineProperties"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "defineProperties", DefineProperties, 2), propertyFlags), ["is"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "is", Is, 2, lengthFlags), propertyFlags), ["seal"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "seal", Seal, 1, lengthFlags), propertyFlags), ["freeze"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "freeze", Freeze, 1), propertyFlags), ["preventExtensions"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "preventExtensions", PreventExtensions, 1), propertyFlags), ["isSealed"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "isSealed", IsSealed, 1), propertyFlags), ["isFrozen"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "isFrozen", IsFrozen, 1), propertyFlags), ["isExtensible"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "isExtensible", IsExtensible, 1), propertyFlags), ["keys"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "keys", Keys, 1, lengthFlags), propertyFlags), ["values"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "values", Values, 1, lengthFlags), propertyFlags), ["setPrototypeOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "setPrototypeOf", SetPrototypeOf, 2, lengthFlags), propertyFlags), ["hasOwn"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "hasOwn", HasOwn, 2, lengthFlags), propertyFlags), }; SetProperties(properties); } /// /// https://tc39.es/ecma262/#sec-object.assign /// private JsValue Assign(JsValue thisObject, JsValue[] arguments) { var to = TypeConverter.ToObject(_realm, arguments.At(0)); if (arguments.Length < 2) { return to; } for (var i = 1; i < arguments.Length; i++) { var nextSource = arguments[i]; if (nextSource.IsNullOrUndefined()) { continue; } var from = TypeConverter.ToObject(_realm, nextSource); var keys = from.GetOwnPropertyKeys(); foreach (var nextKey in keys) { var desc = from.GetOwnProperty(nextKey); if (desc != PropertyDescriptor.Undefined && desc.Enumerable) { var propValue = from.Get(nextKey); to.Set(nextKey, propValue, throwOnError: true); } } } return to; } /// /// https://tc39.es/ecma262/#sec-object.entries /// private JsValue Entries(JsValue thisObject, JsValue[] arguments) { var obj = TypeConverter.ToObject(_realm, arguments.At(0)); var nameList = obj.EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind.KeyValue); return nameList; } /// /// https://tc39.es/ecma262/#sec-object.fromentries /// private JsValue FromEntries(JsValue thisObject, JsValue[] arguments) { var iterable = arguments.At(0); TypeConverter.CheckObjectCoercible(_engine, iterable); var obj = _realm.Intrinsics.Object.Construct(0); var adder = CreateDataPropertyOnObject.Instance; var iterator = arguments.At(0).GetIterator(_realm); IteratorProtocol.AddEntriesFromIterable(obj, iterator, adder); return obj; } /// /// https://tc39.es/ecma262/#sec-object.is /// private static JsValue Is(JsValue thisObject, JsValue[] arguments) { return SameValue(arguments.At(0), arguments.At(1)); } /// /// https://tc39.es/ecma262/#sec-object-value /// protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments) { if (arguments.Length == 0) { return Construct(arguments); } if(arguments[0].IsNullOrUndefined()) { return Construct(arguments); } return TypeConverter.ToObject(_realm, arguments[0]); } /// /// https://tc39.es/ecma262/#sec-object-value /// public ObjectInstance Construct(JsValue[] arguments) { return Construct(arguments, this); } ObjectInstance IConstructor.Construct(JsValue[] arguments, JsValue newTarget) => Construct(arguments, newTarget); private ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { if (!ReferenceEquals(this, newTarget) && !newTarget.IsUndefined()) { return OrdinaryCreateFromConstructor( newTarget, static intrinsics => intrinsics.Object.PrototypeObject, static (Engine engine, Realm _, object? _) => new ObjectInstance(engine)); } if (arguments.Length > 0) { var value = arguments[0]; if (value is ObjectInstance oi) { return oi; } var type = value.Type; if (type is Types.String or Types.Number or Types.Boolean) { return TypeConverter.ToObject(_realm, value); } } return new ObjectInstance(_engine); } internal ObjectInstance Construct(int propertyCount) { var obj = new ObjectInstance(_engine); obj.SetProperties(propertyCount > 0 ? new PropertyDictionary(propertyCount, checkExistingKeys: true) : null); return obj; } /// /// https://tc39.es/ecma262/#sec-object.getprototypeof /// public JsValue GetPrototypeOf(JsValue thisObject, JsValue[] arguments) { var obj = TypeConverter.ToObject(_realm, arguments.At(0)); return obj.Prototype ?? Null; } /// /// https://tc39.es/ecma262/#sec-object.setprototypeof /// private JsValue SetPrototypeOf(JsValue thisObject, JsValue[] arguments) { var oArg = arguments.At(0); TypeConverter.CheckObjectCoercible(_engine, oArg); var prototype = arguments.At(1); if (!prototype.IsObject() && !prototype.IsNull()) { ExceptionHelper.ThrowTypeError(_realm, $"Object prototype may only be an Object or null: {prototype}"); } if (!(oArg is ObjectInstance o)) { return oArg; } if (!o.SetPrototypeOf(prototype)) { ExceptionHelper.ThrowTypeError(_realm); } return o; } /// /// https://tc39.es/ecma262/#sec-object.hasown /// private JsValue HasOwn(JsValue thisObject, JsValue[] arguments) { var o = TypeConverter.ToObject(_realm, arguments.At(0)); var property = TypeConverter.ToPropertyKey(arguments.At(1)); return o.HasOwnProperty(property) ? JsBoolean.True : JsBoolean.False; } /// /// https://tc39.es/ecma262/#sec-object.getownpropertydescriptor /// internal JsValue GetOwnPropertyDescriptor(JsValue thisObject, JsValue[] arguments) { var o = TypeConverter.ToObject(_realm, arguments.At(0)); var p = arguments.At(1); var name = TypeConverter.ToPropertyKey(p); var desc = o.GetOwnProperty(name); return PropertyDescriptor.FromPropertyDescriptor(Engine, desc); } /// /// https://tc39.es/ecma262/#sec-object.getownpropertydescriptors /// private JsValue GetOwnPropertyDescriptors(JsValue thisObject, JsValue[] arguments) { var o = TypeConverter.ToObject(_realm, arguments.At(0)); var ownKeys = o.GetOwnPropertyKeys(); var descriptors = _realm.Intrinsics.Object.Construct(0); foreach (var key in ownKeys) { var desc = o.GetOwnProperty(key); var descriptor = PropertyDescriptor.FromPropertyDescriptor(Engine, desc); if (!ReferenceEquals(descriptor, Undefined)) { descriptors.CreateDataProperty(key, descriptor); } } return descriptors; } /// /// https://tc39.es/ecma262/#sec-object.getownpropertynames /// private JsValue GetOwnPropertyNames(JsValue thisObject, JsValue[] arguments) { var o = TypeConverter.ToObject(_realm, arguments.At(0)); var names = o.GetOwnPropertyKeys(Types.String); return _realm.Intrinsics.Array.ConstructFast(names); } /// /// https://tc39.es/ecma262/#sec-object.getownpropertysymbols /// private JsValue GetOwnPropertySymbols(JsValue thisObject, JsValue[] arguments) { var o = TypeConverter.ToObject(_realm, arguments.At(0)); var keys = o.GetOwnPropertyKeys(Types.Symbol); return _realm.Intrinsics.Array.ConstructFast(keys); } /// /// https://tc39.es/ecma262/#sec-object.create /// private JsValue Create(JsValue thisObject, JsValue[] arguments) { var prototype = arguments.At(0); if (!prototype.IsObject() && !prototype.IsNull()) { ExceptionHelper.ThrowTypeError(_realm, "Object prototype may only be an Object or null: " + prototype); } var obj = Engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); obj._prototype = prototype.IsNull() ? null : prototype.AsObject(); var properties = arguments.At(1); if (!properties.IsUndefined()) { ObjectDefineProperties(obj, properties); } return obj; } /// /// https://tc39.es/ecma262/#sec-object.defineproperty /// private JsValue DefineProperty(JsValue thisObject, JsValue[] arguments) { var o = arguments.At(0) as ObjectInstance; if (o is null) { ExceptionHelper.ThrowTypeError(_realm, "Object.defineProperty called on non-object"); } var p = arguments.At(1); var name = TypeConverter.ToPropertyKey(p); var attributes = arguments.At(2); var desc = PropertyDescriptor.ToPropertyDescriptor(_realm, attributes); o.DefinePropertyOrThrow(name, desc); return arguments.At(0); } /// /// https://tc39.es/ecma262/#sec-object.defineproperties /// private JsValue DefineProperties(JsValue thisObject, JsValue[] arguments) { var o = arguments.At(0) as ObjectInstance; if (o is null) { ExceptionHelper.ThrowTypeError(_realm, "Object.defineProperty called on non-object"); } var properties = arguments.At(1); return ObjectDefineProperties(o, properties); } /// /// https://tc39.es/ecma262/#sec-objectdefineproperties /// private JsValue ObjectDefineProperties(ObjectInstance o, JsValue properties) { var props = TypeConverter.ToObject(_realm, properties); var keys = props.GetOwnPropertyKeys(); var descriptors = new List>(); for (var i = 0; i < keys.Count; i++) { var nextKey = keys[i]; var propDesc = props.GetOwnProperty(nextKey); if (propDesc == PropertyDescriptor.Undefined || !propDesc.Enumerable) { continue; } var descObj = props.UnwrapJsValue(propDesc); var desc = PropertyDescriptor.ToPropertyDescriptor(_realm, descObj); descriptors.Add(new KeyValuePair(nextKey, desc)); } foreach (var pair in descriptors) { o.DefinePropertyOrThrow(pair.Key, pair.Value); } return o; } /// /// https://tc39.es/ecma262/#sec-object.seal /// private JsValue Seal(JsValue thisObject, JsValue[] arguments) { if (arguments.At(0) is not ObjectInstance o) { return arguments.At(0); } var status = SetIntegrityLevel(o, IntegrityLevel.Sealed); if (!status) { ExceptionHelper.ThrowTypeError(_realm); } return o; } /// /// https://tc39.es/ecma262/#sec-object.freeze /// private JsValue Freeze(JsValue thisObject, JsValue[] arguments) { if (arguments.At(0) is not ObjectInstance o) { return arguments.At(0); } var status = SetIntegrityLevel(o, IntegrityLevel.Frozen); if (!status) { ExceptionHelper.ThrowTypeError(_realm); } return o; } /// /// https://tc39.es/ecma262/#sec-setintegritylevel /// private static bool SetIntegrityLevel(ObjectInstance o, IntegrityLevel level) { var status = o.PreventExtensions(); if (!status) { return false; } var keys = o.GetOwnPropertyKeys(); if (level == IntegrityLevel.Sealed) { for (var i = 0; i < keys.Count; i++) { var k = keys[i]; o.DefinePropertyOrThrow(k, new PropertyDescriptor { Configurable = false }); } } else { for (var i = 0; i < keys.Count; i++) { var k = keys[i]; var currentDesc = o.GetOwnProperty(k); if (currentDesc != PropertyDescriptor.Undefined) { PropertyDescriptor desc; if (currentDesc.IsAccessorDescriptor()) { desc = new PropertyDescriptor { Configurable = false }; } else { desc = new PropertyDescriptor { Configurable = false, Writable = false }; } o.DefinePropertyOrThrow(k, desc); } } } return true; } private enum IntegrityLevel { Sealed, Frozen } /// /// https://tc39.es/ecma262/#sec-object.preventextensions /// private JsValue PreventExtensions(JsValue thisObject, JsValue[] arguments) { if (!(arguments.At(0) is ObjectInstance o)) { return arguments.At(0); } if (!o.PreventExtensions()) { ExceptionHelper.ThrowTypeError(_realm); } return o; } /// /// https://tc39.es/ecma262/#sec-object.issealed /// private static JsValue IsSealed(JsValue thisObject, JsValue[] arguments) { if (arguments.At(0) is not ObjectInstance o) { return true; } return TestIntegrityLevel(o, IntegrityLevel.Sealed); } /// /// https://tc39.es/ecma262/#sec-object.isfrozen /// private static JsValue IsFrozen(JsValue thisObject, JsValue[] arguments) { if (arguments.At(0) is not ObjectInstance o) { return true; } return TestIntegrityLevel(o, IntegrityLevel.Frozen); } /// /// https://tc39.es/ecma262/#sec-testintegritylevel /// private static JsValue TestIntegrityLevel(ObjectInstance o, IntegrityLevel level) { if (o.Extensible) { return JsBoolean.False; } foreach (var k in o.GetOwnPropertyKeys()) { var currentDesc = o.GetOwnProperty(k); if (currentDesc != PropertyDescriptor.Undefined) { if (currentDesc.Configurable) { return JsBoolean.False; } if (level == IntegrityLevel.Frozen && currentDesc.IsDataDescriptor()) { if (currentDesc.Writable) { return JsBoolean.False; } } } } return JsBoolean.True; } /// /// https://tc39.es/ecma262/#sec-object.isextensible /// private static JsValue IsExtensible(JsValue thisObject, JsValue[] arguments) { if (arguments.At(0) is not ObjectInstance o) { return false; } return o.Extensible; } /// /// https://tc39.es/ecma262/#sec-object.keys /// private JsValue Keys(JsValue thisObject, JsValue[] arguments) { var o = TypeConverter.ToObject(_realm, arguments.At(0)); return o.EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind.Key); } /// /// https://tc39.es/ecma262/#sec-object.values /// private JsValue Values(JsValue thisObject, JsValue[] arguments) { var o = TypeConverter.ToObject(_realm, arguments.At(0)); return o.EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind.Value); } private sealed class CreateDataPropertyOnObject : ICallable { internal static readonly CreateDataPropertyOnObject Instance = new(); private CreateDataPropertyOnObject() { } public JsValue Call(JsValue thisObject, JsValue[] arguments) { var o = (ObjectInstance) thisObject; var key = arguments.At(0); var value = arguments.At(1); var propertyKey = TypeConverter.ToPropertyKey(key); o.CreateDataPropertyOrThrow(propertyKey, value); return Undefined; } } } }