/// <summary>
/// Defines or redefines the value and attributes of a property. The prototype chain is
/// not searched so if the property exists but only in the prototype chain a new property
/// will be created.
/// </summary>
/// <param name="key"> The property key of the property to modify. </param>
/// <param name="descriptor"> The property value and attributes. </param>
/// <param name="throwOnError"> <c>true</c> to throw an exception if the property could not
/// be set. This can happen if the property is not configurable or the object is sealed. </param>
/// <returns> <c>true</c> if the property was successfully modified; <c>false</c> otherwise. </returns>
public override bool DefineProperty(object key, PropertyDescriptor descriptor, bool throwOnError)
{
// Check for revocation.
if (target == null || handler == null)
{
throw new JavaScriptException(ErrorType.TypeError, "Cannot call 'defineProperty' on a proxy that has been revoked.");
}
// Call the handler, if one exists.
var trap = handler.GetMethod("defineProperty");
if (trap == null)
{
return(target.DefineProperty(key, descriptor, throwOnError));
}
var result = TypeConverter.ToBoolean(trap.CallLateBound(handler, target, key, descriptor.ToObject(Engine)));
// Validate.
if (!result)
{
if (throwOnError)
{
throw new JavaScriptException(ErrorType.TypeError, $"'defineProperty' on proxy: trap returned falsish for property '{TypeConverter.ToString(key)}'.");
}
return(false);
}
var targetDescriptor = target.GetOwnPropertyDescriptor(key);
if (targetDescriptor.Exists)
{
if (!IsCompatiblePropertyDescriptor(target.IsExtensible, descriptor, targetDescriptor))
{
throw new JavaScriptException(ErrorType.TypeError, $"'defineProperty' on proxy: trap returned truish for adding property '{TypeConverter.ToString(key)}' that is incompatible with the existing property in the proxy target.");
}
if (descriptor.Exists && !descriptor.IsConfigurable && targetDescriptor.IsConfigurable)
{
throw new JavaScriptException(ErrorType.TypeError, $"'defineProperty' on proxy: trap returned truish for defining non-configurable property '{TypeConverter.ToString(key)}' which is either non-existent or configurable in the proxy target.");
}
if (!targetDescriptor.IsAccessor && !targetDescriptor.IsConfigurable && targetDescriptor.IsWritable)
{
if (descriptor.Exists && !descriptor.IsWritable)
{
throw new JavaScriptException(ErrorType.TypeError, $"'defineProperty' on proxy: trap returned truish for defining non-configurable property '{TypeConverter.ToString(key)}' which cannot be non-writable, unless there exists a corresponding non-configurable, non-writable own property of the target object.");
}
}
}
else
{
if (!target.IsExtensible)
{
throw new JavaScriptException(ErrorType.TypeError, $"'defineProperty' on proxy: trap returned truish for adding property '{TypeConverter.ToString(key)}' to the non-extensible proxy target.");
}
if (descriptor.Exists && !descriptor.IsConfigurable)
{
throw new JavaScriptException(ErrorType.TypeError, $"'defineProperty' on proxy: trap returned truish for defining non-configurable property '{TypeConverter.ToString(key)}' which is either non-existent or configurable in the proxy target.");
}
}
return(true);
}