using System.Collections.Generic; 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("delegate"); 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); } 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), 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); } 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; } private JsValue Entries(JsValue thisObject, JsValue[] arguments) { var obj = TypeConverter.ToObject(_realm, arguments.At(0)); var nameList = obj.EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind.KeyValue); return nameList; } 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; } private static JsValue Is(JsValue thisObject, JsValue[] arguments) { return SameValue(arguments.At(0), arguments.At(1)); } public ObjectPrototype PrototypeObject { get; private set; } /// /// https://tc39.es/ecma262/#sec-object-value /// public 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); } public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { if (!ReferenceEquals(this, newTarget) && !newTarget.IsUndefined()) { return OrdinaryCreateFromConstructor( newTarget, static intrinsics => intrinsics.Object.PrototypeObject, (engine, realm, state) => new ObjectInstance(engine)); } if (arguments.Length > 0) { var value = arguments[0]; if (value is ObjectInstance oi) { return oi; } var type = value.Type; if (type == Types.String || type == Types.Number || type == 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; } public JsValue GetPrototypeOf(JsValue thisObject, JsValue[] arguments) { var obj = TypeConverter.ToObject(_realm, arguments.At(0)); return obj.Prototype ?? Null; } 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; } 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; } 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); } 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; } public 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); } 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); } 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()) { var jsValues = _engine._jsValueArrayPool.RentArray(2); jsValues[0] = obj; jsValues[1] = properties; DefineProperties(thisObject, jsValues); _engine._jsValueArrayPool.ReturnArray(jsValues); } return obj; } 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); } 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); var props = TypeConverter.ToObject(_realm, properties); var descriptors = new List>(); foreach (var p in props.GetOwnProperties()) { if (!p.Value.Enumerable) { continue; } var descObj = props.Get(p.Key, props); var desc = PropertyDescriptor.ToPropertyDescriptor(_realm, descObj); descriptors.Add(new KeyValuePair(p.Key, desc)); } foreach (var pair in descriptors) { o.DefinePropertyOrThrow(pair.Key, pair.Value); } return o; } private JsValue Seal(JsValue thisObject, JsValue[] arguments) { if (!(arguments.At(0) is ObjectInstance o)) { return arguments.At(0); } var properties = new List>(o.GetOwnProperties()); foreach (var prop in properties) { var propertyDescriptor = prop.Value; if (propertyDescriptor.Configurable) { propertyDescriptor.Configurable = false; FastSetProperty(prop.Key, propertyDescriptor); } o.DefinePropertyOrThrow(prop.Key, propertyDescriptor); } o.PreventExtensions(); return o; } private static JsValue Freeze(JsValue thisObject, JsValue[] arguments) { if (!(arguments.At(0) is ObjectInstance o)) { return arguments.At(0); } foreach (var p in o.GetOwnProperties()) { var desc = o.GetOwnProperty(p.Key); if (desc.IsDataDescriptor()) { if (desc.Writable) { var mutable = desc; mutable.Writable = false; desc = mutable; } } if (desc.Configurable) { var mutable = desc; mutable.Configurable = false; desc = mutable; } o.DefinePropertyOrThrow(p.Key, desc); } o.PreventExtensions(); return o; } private static JsValue PreventExtensions(JsValue thisObject, JsValue[] arguments) { if (!(arguments.At(0) is ObjectInstance o)) { return arguments.At(0); } o.PreventExtensions(); return o; } private static JsValue IsSealed(JsValue thisObject, JsValue[] arguments) { if (!(arguments.At(0) is ObjectInstance o)) { return arguments.At(0); } foreach (var prop in o.GetOwnProperties()) { if (prop.Value.Configurable) { return false; } } if (o.Extensible == false) { return true; } return false; } private static JsValue IsFrozen(JsValue thisObject, JsValue[] arguments) { if (!(arguments.At(0) is ObjectInstance o)) { return arguments.At(0); } foreach (var pair in o.GetOwnProperties()) { var desc = pair.Value; if (desc.IsDataDescriptor()) { if (desc.Writable) { return false; } } if (desc.Configurable) { return false; } } if (o.Extensible == false) { return true; } return false; } private static JsValue IsExtensible(JsValue thisObject, JsValue[] arguments) { if (!(arguments.At(0) is ObjectInstance o)) { return arguments.At(0); } return o.Extensible; } private JsValue Keys(JsValue thisObject, JsValue[] arguments) { var o = TypeConverter.ToObject(_realm, arguments.At(0)); return o.EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind.Key); } 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 CreateDataPropertyOnObject(); 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; } } } }