using System.Collections.Generic; using Jint.Native.Date; using Jint.Native.String; using Jint.Runtime; using Jint.Runtime.Descriptors; namespace Jint.Native.Object { public class ObjectInstance { public ObjectInstance(Engine engine) { Engine = engine; Properties = new Dictionary(); } public Engine Engine { get; set; } public IDictionary Properties { get; private set; } /// /// The prototype of this object. /// public ObjectInstance Prototype { get; set; } /// /// If true, own properties may be added to the /// object. /// public bool Extensible { get; set; } /// /// A String value indicating a specification defined /// classification of objects. /// public virtual string Class { get { return "Object"; } } /// /// Returns the value of the named property. /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.3 /// /// /// public virtual JsValue Get(string propertyName) { var desc = GetProperty(propertyName); if (desc == PropertyDescriptor.Undefined) { return JsValue.Undefined; } if (desc.IsDataDescriptor()) { return desc.Value.HasValue ? desc.Value.Value : Undefined.Instance; } var getter = desc.Get.HasValue ? desc.Get.Value : Undefined.Instance; if (getter.IsUndefined()) { return Undefined.Instance; } // if getter is not undefined it must be ICallable var callable = getter.TryCast(); return callable.Call(this, Arguments.Empty); } public void Set(string name, JsValue value) { if (!HasProperty(name)) { DefineOwnProperty(name, new PropertyDescriptor(value, true, true, true), false); } else { Put(name, value, false); } } /// /// Returns the Property Descriptor of the named /// own property of this object, or undefined if /// absent. /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.1 /// /// /// public virtual PropertyDescriptor GetOwnProperty(string propertyName) { PropertyDescriptor x; if (Properties.TryGetValue(propertyName, out x)) { /* Spec implementation PropertyDescriptor d; if (x.IsDataDescriptor()) { d = new PropertyDescriptor(x.As()); } else { d = new PropertyDescriptor(x.As()); } return d; */ // optimmized implementation return x; } return PropertyDescriptor.Undefined; } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.2 /// /// /// public PropertyDescriptor GetProperty(string propertyName) { var prop = GetOwnProperty(propertyName); if (prop != PropertyDescriptor.Undefined) { return prop; } if(Prototype == null) { return PropertyDescriptor.Undefined; } return Prototype.GetProperty(propertyName); } /// /// Sets the specified named property to the value /// of the second parameter. The flag controls /// failure handling. /// /// /// /// public void Put(string propertyName, JsValue value, bool throwOnError) { if (!CanPut(propertyName)) { if (throwOnError) { throw new JavaScriptException(Engine.TypeError); } return; } var ownDesc = GetOwnProperty(propertyName); if (ownDesc.IsDataDescriptor()) { var valueDesc = new PropertyDescriptor(value: value, writable: null, enumerable:null, configurable:null); DefineOwnProperty(propertyName, valueDesc, throwOnError); return; } // property is an accessor or inherited var desc = GetProperty(propertyName); if (desc.IsAccessorDescriptor()) { var setter = desc.Set.Value.TryCast(); setter.Call(new JsValue(this), new [] {value}); } else { var newDesc = new PropertyDescriptor(value, true, true, true); DefineOwnProperty(propertyName, newDesc, throwOnError); } } /// /// Returns a Boolean value indicating whether a /// [[Put]] operation with PropertyName can be /// performed. /// http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.4 /// /// /// public bool CanPut(string propertyName) { var desc = GetOwnProperty(propertyName); if (desc != PropertyDescriptor.Undefined) { if (desc.IsAccessorDescriptor()) { if (!desc.Set.HasValue || desc.Set.Value.IsUndefined()) { return false; } return true; } return desc.Writable.HasValue && desc.Writable.Value.AsBoolean(); } if (Prototype == null) { return Extensible; } var inherited = Prototype.GetProperty(propertyName); if (inherited == PropertyDescriptor.Undefined) { return Extensible; } if (inherited.IsAccessorDescriptor()) { if (!inherited.Set.HasValue || inherited.Set.Value.IsUndefined()) { return false; } return true; } if (!Extensible) { return false; } else { return inherited.Writable.HasValue && inherited.Writable.Value.AsBoolean(); } } /// /// Returns a Boolean value indicating whether the /// object already has a property with the given /// name. /// /// /// public bool HasProperty(string propertyName) { return GetProperty(propertyName) != PropertyDescriptor.Undefined; } /// /// Removes the specified named own property /// from the object. The flag controls failure /// handling. /// /// /// /// public virtual bool Delete(string propertyName, bool throwOnError) { var desc = GetOwnProperty(propertyName); if (desc == PropertyDescriptor.Undefined) { return true; } if (desc.Configurable.HasValue && desc.Configurable.Value.AsBoolean()) { Properties.Remove(propertyName); return true; } else { if (throwOnError) { throw new JavaScriptException(Engine.TypeError); } return false; } } /// /// Hint is a String. Returns a default value for the /// object. /// /// /// public JsValue DefaultValue(Types hint) { if ((hint == Types.String) || (hint == Types.None && this is StringInstance) || this is DateInstance) { var toString = Get("toString").TryCast(); if (toString != null) { var str = toString.Call(new JsValue(this), Arguments.Empty); if (str.IsPrimitive()) { return str; } } var valueOf = Get("valueOf").TryCast(); if (valueOf != null) { var val = valueOf.Call(new JsValue(this), Arguments.Empty); if (val.IsPrimitive()) { return val; } } throw new JavaScriptException(Engine.TypeError); } if ((hint == Types.Number) || (hint == Types.None)) { var valueOf = Get("valueOf").TryCast(); if (valueOf != null) { var val = valueOf.Call(new JsValue(this), Arguments.Empty); if (val.IsPrimitive()) { return val; } } var toString = Get("toString").TryCast(); if (toString != null) { var str = toString.Call(new JsValue(this), Arguments.Empty); if (str.IsPrimitive()) { return str; } } throw new JavaScriptException(Engine.TypeError); } return ToString(); } /// /// Creates or alters the named own property to /// have the state described by a Property /// Descriptor. The flag controls failure handling. /// /// /// /// /// public virtual bool DefineOwnProperty(string propertyName, PropertyDescriptor desc, bool throwOnError) { var current = GetOwnProperty(propertyName); if (current == PropertyDescriptor.Undefined) { if (!Extensible) { if (throwOnError) { throw new JavaScriptException(Engine.TypeError); } return false; } else { if (desc.IsGenericDescriptor() || desc.IsDataDescriptor()) { Properties[propertyName] = new PropertyDescriptor(desc); } else { Properties[propertyName] = new PropertyDescriptor(desc); } } return true; } // Step 5 if (!current.Configurable.HasValue && !current.Enumerable.HasValue && !(current.IsDataDescriptor() && current.Writable.HasValue)) { if (!desc.IsDataDescriptor()) { return true; } } // Step 6 var configurableIsSame = current.Configurable.HasValue ? desc.Configurable.HasValue && (current.Configurable.Value == desc.Configurable.Value) : !desc.Configurable.HasValue; var enumerableIsSame = current.Enumerable.HasValue ? desc.Enumerable.HasValue && (current.Enumerable.Value == desc.Enumerable.Value) : !desc.Enumerable.HasValue; var writableIsSame = true; var valueIsSame = true; if (current.IsDataDescriptor() && desc.IsDataDescriptor()) { var currentDataDescriptor = current; var descDataDescriptor = desc; writableIsSame = currentDataDescriptor.Writable.HasValue ? descDataDescriptor.Writable.HasValue && (currentDataDescriptor.Writable.Value == descDataDescriptor.Writable.Value) : !descDataDescriptor.Writable.HasValue; var valueA = currentDataDescriptor.Value.HasValue ? currentDataDescriptor.Value.Value : Undefined.Instance; var valueB = descDataDescriptor.Value.HasValue ? descDataDescriptor.Value.Value : Undefined.Instance; valueIsSame = ExpressionInterpreter.SameValue(valueA, valueB); } else if (current.IsAccessorDescriptor() && desc.IsAccessorDescriptor()) { var currentAccessorDescriptor = current; var descAccessorDescriptor = desc; var getValueA = currentAccessorDescriptor.Get.HasValue ? currentAccessorDescriptor.Get.Value : Undefined.Instance; var getValueB = descAccessorDescriptor.Get.HasValue ? descAccessorDescriptor.Get.Value : Undefined.Instance; var setValueA = currentAccessorDescriptor.Set.HasValue ? currentAccessorDescriptor.Set.Value : Undefined.Instance; var setValueB = descAccessorDescriptor.Set.HasValue ? descAccessorDescriptor.Set.Value : Undefined.Instance; valueIsSame = ExpressionInterpreter.SameValue(getValueA, getValueB) && ExpressionInterpreter.SameValue(setValueA, setValueB); } else { valueIsSame = false; } if (configurableIsSame && enumerableIsSame && writableIsSame && valueIsSame) { return true; } if (!current.Configurable.HasValue || !current.Configurable.Value.AsBoolean()) { if (desc.Configurable.HasValue && desc.Configurable.Value.AsBoolean()) { if (throwOnError) { throw new JavaScriptException(Engine.TypeError); } return false; } if (desc.Enumerable.HasValue && (!current.Enumerable.HasValue || desc.Enumerable.Value != current.Enumerable.Value)) { if (throwOnError) { throw new JavaScriptException(Engine.TypeError); } return false; } } if (!desc.IsGenericDescriptor()) { if (current.IsDataDescriptor() != desc.IsDataDescriptor()) { if (!current.Configurable.HasValue || !current.Configurable.Value.AsBoolean()) { if (throwOnError) { throw new JavaScriptException(Engine.TypeError); } return false; } if (current.IsDataDescriptor()) { Properties[propertyName] = current = new PropertyDescriptor( get: Undefined.Instance, set: Undefined.Instance, enumerable: current.Enumerable.HasValue && current.Enumerable.Value.AsBoolean(), configurable: current.Configurable.HasValue && current.Configurable.Value.AsBoolean() ); } else { Properties[propertyName] = current = new PropertyDescriptor( value: Undefined.Instance, writable: null, enumerable: current.Enumerable.HasValue && current.Enumerable.Value.AsBoolean(), configurable: current.Configurable.HasValue && current.Configurable.Value.AsBoolean() ); } } else if (current.IsDataDescriptor() && desc.IsDataDescriptor()) { if (!current.Configurable.HasValue || current.Configurable.Value.AsBoolean() == false) { if (!current.Writable.HasValue || !current.Writable.Value.AsBoolean() && desc.Writable.HasValue && desc.Writable.Value.AsBoolean()) { if (throwOnError) { throw new JavaScriptException(Engine.TypeError); } return false; } if (!current.Writable.Value.AsBoolean()) { if (desc.Value.HasValue && !valueIsSame) { if (throwOnError) { throw new JavaScriptException(Engine.TypeError); } return false; } } } if (!desc.Writable.HasValue && current.Writable.HasValue) { desc.Enumerable = current.Enumerable; } } else if (current.IsAccessorDescriptor() && desc.IsAccessorDescriptor()) { if (!current.Configurable.HasValue || !current.Configurable.Value.AsBoolean()) { if ((desc.Set.HasValue && !ExpressionInterpreter.SameValue(desc.Set.Value, current.Set.HasValue ? current.Set.Value : Undefined.Instance)) || (desc.Get.HasValue && !ExpressionInterpreter.SameValue(desc.Get.Value, current.Get.HasValue ? current.Get.Value : Undefined.Instance))) { if (throwOnError) { throw new JavaScriptException(Engine.TypeError); } return false; } } } } if (desc.Value.HasValue) { current.Value = desc.Value; } if (desc.Writable.HasValue) { current.Writable = desc.Writable; } if (desc.Enumerable.HasValue) { current.Enumerable = desc.Enumerable; } if (desc.Configurable.HasValue) { current.Configurable = desc.Configurable; } if (desc.Get.HasValue) { current.Get = desc.Get; } if (desc.Set.HasValue) { current.Set = desc.Set; } return true; } /// /// Optimized version of [[Put]] when the property is known to be undeclared already /// /// /// /// /// /// public void FastAddProperty(string name, JsValue value, bool writable, bool enumerable, bool configurable) { Properties.Add(name, new PropertyDescriptor(value, writable, enumerable, configurable)); } /// /// Optimized version of [[Put]] when the property is known to be already declared /// /// /// public void FastSetProperty(string name, PropertyDescriptor value) { Properties[name] = value; } public override string ToString() { return TypeConverter.ToString(this); } } }