//     HELPER FUNCTIONS
        //_________________________________________________________________________________________

        /// <summary>
        /// Checks whether the given descriptor is compatible with the current descriptor.
        /// </summary>
        /// <param name="descriptor"> The new descriptor. </param>
        /// <param name="current"> The descriptor corresponding to the currently existing property. </param>
        /// <returns> <c>true</c> if the new descriptor is compatible with the old one; <c>false</c> otherwise. </returns>
        internal static bool IsCompatible(PropertyDescriptor descriptor, PropertyDescriptor current)
        {
            // If the current property is configurable, then allow the modification.
            if (current.IsConfigurable)
            {
                return(true);
            }

            // If both properties are accessors, then they must match exactly.
            if (descriptor.value is PropertyAccessorValue descriptorAccessor)
            {
                if (current.value is PropertyAccessorValue currentAccessor)
                {
                    return(TypeComparer.SameValue(descriptorAccessor.Getter, currentAccessor.Getter) &&
                           TypeComparer.SameValue(descriptorAccessor.Setter, currentAccessor.Setter) &&
                           descriptor.Attributes == current.Attributes);
                }
                return(false);
            }

            // If this is a data property, and writable is false, then the properties must match exactly.
            if (current.IsWritable == false)
            {
                return(TypeComparer.SameValue(descriptor.Value, current.Value) &&
                       descriptor.Attributes == current.Attributes);
            }

            // Otherwise, if the data property is writable, then IsEnumerable and IsConfigurable must match.
            // Value changes are allowed, and making the property non-writable is also allowed.
            return(!descriptor.IsConfigurable && descriptor.IsEnumerable == current.IsEnumerable);
        }
Exemple #2
0
        /// <summary>
        /// Gets the value of the property with the given name.
        /// </summary>
        /// <param name="key"> The property key (either a string or a Symbol). </param>
        /// <param name="thisValue"> The value of the "this" keyword inside a getter. </param>
        /// <returns> The value of the property, or <c>null</c> if the property doesn't exist. </returns>
        /// <remarks> The prototype chain is searched if the property does not exist directly on
        /// this object. </remarks>
        public override object GetPropertyValue(object key, object thisValue)
        {
            // Check for revocation.
            if (target == null || handler == null)
            {
                throw new JavaScriptException(ErrorType.TypeError, "Cannot call 'get' on a proxy that has been revoked.");
            }

            // Call the handler, if one exists.
            var trap = handler.GetMethod("get");

            if (trap == null)
            {
                return(target.GetPropertyValue(key, thisValue));
            }
            var result = trap.CallLateBound(handler, target, key, thisValue);

            // Validate.
            var targetDescriptor = target.GetOwnPropertyDescriptor(key);

            if (targetDescriptor.Exists && !targetDescriptor.IsConfigurable)
            {
                if (!targetDescriptor.IsAccessor && !targetDescriptor.IsWritable && !TypeComparer.SameValue(result, targetDescriptor.Value))
                {
                    throw new JavaScriptException(ErrorType.TypeError, $"'get' on proxy: property '{TypeConverter.ToString(key)}' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '{TypeConverter.ToString(targetDescriptor.Value)}' but got '{TypeConverter.ToString(result)}').");
                }
                if (targetDescriptor.IsAccessor && targetDescriptor.Getter == null && result != null && result != Undefined.Value)
                {
                    throw new JavaScriptException(ErrorType.TypeError, $"'get' on proxy: property '{TypeConverter.ToString(key)}' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '{TypeConverter.ToString(result)}').");
                }
            }
            return(result);
        }
Exemple #3
0
        public void SameValue()
        {
            // undefined
            Assert.AreEqual(true, TypeComparer.SameValue(Undefined.Value, Undefined.Value));
            Assert.AreEqual(false, TypeComparer.SameValue(Undefined.Value, Null.Value));
            Assert.AreEqual(false, TypeComparer.SameValue(Undefined.Value, 0));
            Assert.AreEqual(true, TypeComparer.SameValue(null, null));
            Assert.AreEqual(true, TypeComparer.SameValue(null, Undefined.Value));
            Assert.AreEqual(false, TypeComparer.SameValue(null, Null.Value));
            Assert.AreEqual(false, TypeComparer.SameValue(null, 0));

            // null
            Assert.AreEqual(true, TypeComparer.SameValue(Null.Value, Null.Value));
            Assert.AreEqual(false, TypeComparer.SameValue(Null.Value, Undefined.Value));
            Assert.AreEqual(false, TypeComparer.SameValue(Null.Value, 0));

            // number
            Assert.AreEqual(true, TypeComparer.SameValue(+0.0, +0.0));
            Assert.AreEqual(true, TypeComparer.SameValue(-0.0, -0.0));
            Assert.AreEqual(false, TypeComparer.SameValue(+0.0, -0.0));
            Assert.AreEqual(false, TypeComparer.SameValue(-0.0, +0.0));
            Assert.AreEqual(true, TypeComparer.SameValue(1, 1));
            Assert.AreEqual(false, TypeComparer.SameValue(0, 1));
            Assert.AreEqual(true, TypeComparer.SameValue(5, 5.0));
            Assert.AreEqual(true, TypeComparer.SameValue(5.0, 5));
            Assert.AreEqual(true, TypeComparer.SameValue(5.0, 5.0));
            Assert.AreEqual(false, TypeComparer.SameValue(5.0, 6.0));
            Assert.AreEqual(true, TypeComparer.SameValue(double.NaN, double.NaN));
            Assert.AreEqual(false, TypeComparer.SameValue(double.NaN, 5));
            Assert.AreEqual(false, TypeComparer.SameValue(double.NaN, 5.0));
            Assert.AreEqual(false, TypeComparer.SameValue(0, "0"));

            // string
            Assert.AreEqual(true, TypeComparer.SameValue("", ""));
            Assert.AreEqual(true, TypeComparer.SameValue("a", "a"));
            Assert.AreEqual(false, TypeComparer.SameValue("a", "b"));
            Assert.AreEqual(false, TypeComparer.SameValue("0", 0));

            // bool
            Assert.AreEqual(true, TypeComparer.SameValue(false, false));
            Assert.AreEqual(true, TypeComparer.SameValue(true, true));
            Assert.AreEqual(false, TypeComparer.SameValue(true, false));
            Assert.AreEqual(false, TypeComparer.SameValue(false, 0));

            // object
            var engine  = new ScriptEngine();
            var temp1   = engine.Object.Construct();
            var temp2   = engine.Object.Construct();
            var number1 = engine.Number.Construct(5.0);

            Assert.AreEqual(true, TypeComparer.SameValue(temp1, temp1));
            Assert.AreEqual(false, TypeComparer.SameValue(temp1, temp2));
            Assert.AreEqual(true, TypeComparer.SameValue(number1, number1));
            Assert.AreEqual(false, TypeComparer.SameValue(number1, 5.0));
        }
Exemple #4
0
        /// <summary>
        /// Sets the value of the property with the given name.  If a property with the given name
        /// does not exist, or exists in the prototype chain (and is not a setter) then a new
        /// property is created.
        /// </summary>
        /// <param name="key"> The property key of the property to set. </param>
        /// <param name="value"> The value to set the property to.  This must be a javascript
        /// primitive (double, string, etc) or a class derived from <see cref="ObjectInstance"/>. </param>
        /// <param name="thisValue"> The value of the "this" keyword inside a setter. </param>
        /// <param name="throwOnError"> <c>true</c> to throw an exception if the property could not
        /// be set (i.e. if the property is read-only or if the object is not extensible and a new
        /// property needs to be created). </param>
        /// <returns> <c>false</c> if <paramref name="throwOnError"/> is false and an error
        /// occurred; <c>true</c> otherwise. </returns>
        public override bool SetPropertyValue(object key, object value, object thisValue, bool throwOnError)
        {
            // Check for revocation.
            if (target == null || handler == null)
            {
                throw new JavaScriptException(ErrorType.TypeError, "Cannot call 'set' on a proxy that has been revoked.");
            }

            // Call the handler, if one exists.
            var trap = handler.GetMethod("set");

            if (trap == null)
            {
                return(target.SetPropertyValue(key, value, thisValue, throwOnError));
            }
            var result = TypeConverter.ToBoolean(trap.CallLateBound(handler, target, key, value, thisValue));

            if (!result)
            {
                return(false);
            }

            // Validate.
            var targetDescriptor = target.GetOwnPropertyDescriptor(key);

            if (targetDescriptor.Exists && !targetDescriptor.IsConfigurable)
            {
                if (!targetDescriptor.IsAccessor && !targetDescriptor.IsWritable && !TypeComparer.SameValue(value, targetDescriptor.Value))
                {
                    throw new JavaScriptException(ErrorType.TypeError, $"'set' on proxy: trap returned truish for property '{TypeConverter.ToString(key)}' which exists in the proxy target as a non-configurable and non-writable data property with a different value.");
                }
                if (targetDescriptor.IsAccessor && targetDescriptor.Setter == null)
                {
                    throw new JavaScriptException(ErrorType.TypeError, $"'set' on proxy: trap returned truish for property '{TypeConverter.ToString(key)}' which exists in the proxy target as a non-configurable and non-writable accessor property without a setter.");
                }
            }
            return(true);
        }
Exemple #5
0
 public static bool Is(object value1, object value2)
 {
     return(TypeComparer.SameValue(value1, value2));
 }